diff --git a/.claude/skills/testing-hashql/references/compiletest-guide.md b/.claude/skills/testing-hashql/references/compiletest-guide.md index 1b2ceac669c..36e68b704ae 100644 --- a/.claude/skills/testing-hashql/references/compiletest-guide.md +++ b/.claude/skills/testing-hashql/references/compiletest-guide.md @@ -236,7 +236,7 @@ cargo run -p hashql-compiletest suites --json ### Suite Categories - `parse/*` - Parsing tests (e.g., `parse/syntax-dump`) -- `ast/lowering/*` - AST lowering phases +- `ast/lower/*` - AST lowering phases - `hir/lower/*` - HIR lowering phases - `hir/reify` - HIR generation from AST - `mir/*` - MIR passes and generation diff --git a/Cargo.lock b/Cargo.lock index cbe980da9c5..cb9b958b29d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4058,15 +4058,10 @@ dependencies = [ name = "hashql-ast" version = "0.0.0" dependencies = [ - "derive_more", - "enum-iterator", - "foldhash 0.2.0", - "hashbrown 0.17.0", "hashql-compiletest", "hashql-core", "hashql-diagnostics", "simple-mermaid", - "tracing", ] [[package]] diff --git a/libs/@local/graph/api/src/rest/hashql/compile.rs b/libs/@local/graph/api/src/rest/hashql/compile.rs index a18ed91e4d0..b56c9738864 100644 --- a/libs/@local/graph/api/src/rest/hashql/compile.rs +++ b/libs/@local/graph/api/src/rest/hashql/compile.rs @@ -68,7 +68,7 @@ impl<'heap> Compilation<'heap> { let Success { value: types, advisories, - } = hashql_ast::lowering::lower(sym::path::main, &mut ast, &env, &modules) + } = hashql_ast::lower::lower(sym::path::main, &mut ast, &env, &modules, &mut *scratch) .map_category(|category| { HashQlDiagnosticCategory::Ast(AstDiagnosticCategory::Lowering(category)) }) diff --git a/libs/@local/hashql/ast/Cargo.toml b/libs/@local/hashql/ast/Cargo.toml index 41fbcbae2ea..08373e9b04b 100644 --- a/libs/@local/hashql/ast/Cargo.toml +++ b/libs/@local/hashql/ast/Cargo.toml @@ -16,12 +16,7 @@ hashql-diagnostics = { workspace = true, public = true } # Private workspace dependencies # Private third-party dependencies -derive_more = { workspace = true, features = ["display"] } -enum-iterator = { workspace = true } -foldhash = { workspace = true } -hashbrown = { workspace = true } simple-mermaid = { workspace = true } -tracing = { workspace = true } [dev-dependencies] hashql-compiletest = { workspace = true } diff --git a/libs/@local/hashql/ast/src/error.rs b/libs/@local/hashql/ast/src/error.rs index 1b3ad7ec5be..dcd2b116685 100644 --- a/libs/@local/hashql/ast/src/error.rs +++ b/libs/@local/hashql/ast/src/error.rs @@ -3,7 +3,7 @@ use alloc::borrow::Cow; use hashql_core::span::SpanId; use hashql_diagnostics::{Diagnostic, category::DiagnosticCategory}; -use crate::lowering::error::LoweringDiagnosticCategory; +use crate::lower::error::LoweringDiagnosticCategory; pub type AstDiagnostic = Diagnostic; diff --git a/libs/@local/hashql/ast/src/format/dump.rs b/libs/@local/hashql/ast/src/format/dump.rs index 30d00aa85f2..e10ded95ee6 100644 --- a/libs/@local/hashql/ast/src/format/dump.rs +++ b/libs/@local/hashql/ast/src/format/dump.rs @@ -27,14 +27,12 @@ use crate::node::{ expr::{ AsExpr, CallExpr, ClosureExpr, DictExpr, Expr, ExprKind, FieldExpr, IfExpr, IndexExpr, InputExpr, LetExpr, ListExpr, LiteralExpr, NewTypeExpr, StructExpr, TupleExpr, TypeExpr, - UseExpr, call::{Argument, LabeledArgument}, closure::{ClosureParam, ClosureSignature}, dict::DictEntry, list::ListElement, r#struct::StructEntry, tuple::TupleElement, - r#use::{Glob, UseBinding, UseKind}, }, generic::{GenericArgument, GenericConstraint, GenericParam, Generics}, id::NodeId, @@ -352,62 +350,6 @@ impl_syntax_dump!(struct TypeExpr(name); []constraints value body); impl_syntax_dump!(struct NewTypeExpr(name); []constraints value body); -impl SyntaxDump for UseBinding<'_> { - fn syntax_dump(&self, fmt: &mut Formatter, depth: usize) -> fmt::Result { - let Self { - id, - span, - name, - alias, - } = self; - - let mut properties = vec![format!("name: {name}")]; - if let Some(alias) = alias { - properties.push(format!("alias: {alias}")); - } - - write_header( - fmt, - depth, - "UseBinding", - Some(*id), - Some(*span), - Some(&properties.join(", ")), - ) - } -} - -impl SyntaxDump for Glob { - fn syntax_dump(&self, fmt: &mut Formatter, depth: usize) -> fmt::Result { - let Self { id, span } = self; - - write_header(fmt, depth, "Glob", Some(*id), Some(*span), None) - } -} - -impl SyntaxDump for UseKind<'_> { - fn syntax_dump(&self, fmt: &mut Formatter, depth: usize) -> fmt::Result { - match self { - UseKind::Named(bindings) => { - write_header(fmt, depth, "UseKind", None, None, Some("Named"))?; - - for binding in bindings { - binding.syntax_dump(fmt, depth + 1)?; - } - - Ok(()) - } - UseKind::Glob(glob) => { - write_header(fmt, depth, "UseKind", None, None, Some("Glob"))?; - - glob.syntax_dump(fmt, depth + 1) - } - } - } -} - -impl_syntax_dump!(struct UseExpr(); path kind body); - impl_syntax_dump!(struct InputExpr(name); r#type ?default); #[rustfmt::skip] @@ -478,11 +420,6 @@ impl SyntaxDump for ExprKind<'_> { new_type_expr.syntax_dump(fmt, depth + 1) } - Self::Use(use_expr) => { - write_header(fmt, depth, "ExprKind", None, None, Some("Use"))?; - - use_expr.syntax_dump(fmt, depth + 1) - } Self::Input(input_expr) => { write_header(fmt, depth, "ExprKind", None, None, Some("Input"))?; diff --git a/libs/@local/hashql/ast/src/lib.rs b/libs/@local/hashql/ast/src/lib.rs index 74ff63dc56e..988acb364b0 100644 --- a/libs/@local/hashql/ast/src/lib.rs +++ b/libs/@local/hashql/ast/src/lib.rs @@ -1,47 +1,16 @@ -//! # HashQL Abstract Syntax Tree +//! HashQL abstract syntax tree and lowering pipeline. //! -//! This crate defines the Abstract Syntax Tree (AST) for HashQL, a side-effect free, -//! purely functional query language designed for bi-temporal graph databases. +//! This crate defines the AST for HashQL and the [`lower`] pipeline that +//! transforms it into a form suitable for HIR construction. The AST is +//! frontend-agnostic: nodes are defined independently of any syntax, with +//! J-Expr as the current primary parser. //! -//! ## Overview +//! # Modules //! -//! The HashQL AST provides a structured representation of HashQL programs after parsing. -//! It captures the hierarchical structure of the code with nodes for expressions, types, -//! and declarations. This AST is the foundation for subsequent compilation phases including -//! type checking, optimization, and evaluation. -//! -//! ## Key Features -//! -//! - **Frontend Agnostic**: The AST is designed to be independent of any particular syntax. While -//! the current primary interface is through J-Expr (JSON-based expressions), the AST can support -//! multiple frontend syntaxes. -//! -//! - **Memory Efficient**: Uses arena allocation through a custom [`heap`] module to minimize -//! allocations and improve performance during parsing and transformation. -//! -//! - **Source Tracking**: Each node maintains its location in the source code via span identifiers. -//! -//! - **Comprehensive Language Model**: Supports the full range of language constructs in HashQL, -//! including expressions, types, path references, and special forms. -//! -//! ## Core Modules -//! -//! - [`node`]: Defines the AST node types that represent language constructs -//! - [`lowering`]: Defines the lowering process for AST nodes, converting them into a more -//! optimized form suitable for conversion into the HIR. -//! -//! ## Special Forms -//! -//! HashQL implements several language constructs as "special forms" that are initially parsed -//! as function calls and then transformed into specialized AST nodes. These include: -//! -//! - `let` for variable binding -//! - `if` for conditional expressions -//! - `fn` for closure definitions -//! - `use` for module imports -//! - Field and index access expressions -//! -//! [`heap`]: hashql_core::heap +//! - [`node`]: AST node types (expressions, types, paths, generics). +//! - [`lower`]: Lowering pipeline (expansion, sanitization, type extraction). +//! - [`visit`]: Visitor trait for AST traversal. +//! - [`format`](mod@format): Debug formatting for AST dumps. //! //! ## Workspace dependencies #![cfg_attr(doc, doc = simple_mermaid::mermaid!("../docs/dependency-diagram.mmd"))] @@ -50,6 +19,7 @@ // Language Features coverage_attribute, macro_metavar_expr_concat, + default_field_values, // Library Features allocator_api, @@ -61,6 +31,6 @@ extern crate alloc; pub mod error; pub mod format; -pub mod lowering; +pub mod lower; pub mod node; pub mod visit; diff --git a/libs/@local/hashql/ast/src/lowering/error.rs b/libs/@local/hashql/ast/src/lower/error.rs similarity index 69% rename from libs/@local/hashql/ast/src/lowering/error.rs rename to libs/@local/hashql/ast/src/lower/error.rs index 9da864c28f8..045120539dc 100644 --- a/libs/@local/hashql/ast/src/lowering/error.rs +++ b/libs/@local/hashql/ast/src/lower/error.rs @@ -4,9 +4,7 @@ use hashql_core::span::SpanId; use hashql_diagnostics::{Diagnostic, category::DiagnosticCategory}; use super::{ - import_resolver::error::ImportResolverDiagnosticCategory, - sanitizer::SanitizerDiagnosticCategory, - special_form_expander::error::SpecialFormExpanderDiagnosticCategory, + expander::error::ExpanderDiagnosticCategory, sanitizer::SanitizerDiagnosticCategory, type_extractor::error::TypeExtractorDiagnosticCategory, }; @@ -14,9 +12,9 @@ pub type LoweringDiagnostic = Diagnostic; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum LoweringDiagnosticCategory { - Expander(SpecialFormExpanderDiagnosticCategory), + Expander(ExpanderDiagnosticCategory), Sanitizer(SanitizerDiagnosticCategory), - Resolver(ImportResolverDiagnosticCategory), + Extractor(TypeExtractorDiagnosticCategory), } @@ -31,9 +29,8 @@ impl DiagnosticCategory for LoweringDiagnosticCategory { fn subcategory(&self) -> Option<&dyn DiagnosticCategory> { match self { - Self::Expander(special_form) => Some(special_form), + Self::Expander(expander) => Some(expander), Self::Sanitizer(sanitizer) => Some(sanitizer), - Self::Resolver(resolver) => Some(resolver), Self::Extractor(extractor) => Some(extractor), } } diff --git a/libs/@local/hashql/ast/src/lower/expander/access.rs b/libs/@local/hashql/ast/src/lower/expander/access.rs new file mode 100644 index 00000000000..ec4e99ab53b --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/access.rs @@ -0,0 +1,138 @@ +use core::mem; + +use hashql_core::{ + heap::BumpAllocator, + span::SpanId, + symbol::{Ident, IdentKind, sym}, + value::Primitive, +}; + +use super::Expander; +use crate::{ + lower::expander::error, + node::{ + expr::{CallExpr, Expr, ExprKind, FieldExpr, call::Argument}, + id::NodeId, + }, +}; + +/// Extract a field identifier from an argument. +/// +/// Handles both named field access (identifiers like `name`) and indexed field +/// access (integer literals like `0` for tuple fields). Integer literals are +/// validated for bounds. +fn argument_to_field<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + argument: &Argument<'heap>, +) -> Option> { + // Integer literal for tuple field access + if let ExprKind::Literal(literal) = &argument.value.kind { + if let Some(annotation) = &literal.r#type { + expander + .diagnostics + .push(error::field_literal_type_annotation(annotation.span)); + } + + let Primitive::Integer(integer) = literal.kind else { + expander + .diagnostics + .push(error::invalid_field_literal_type(literal.span)); + return None; + }; + + if integer.as_usize().is_none() { + expander + .diagnostics + .push(error::field_index_out_of_bounds(literal.span)); + return None; + } + + return Some(Ident { + span: literal.span, + value: integer.as_symbol(), + kind: IdentKind::Lexical, + }); + } + + // Named field access + if let ExprKind::Path(path) = &argument.value.kind + && let Some(&ident) = path.as_ident() + { + return Some(ident); + } + + expander + .diagnostics + .push(error::invalid_access_field(argument)); + + None +} + +fn lower_access_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + value: &mut Argument<'heap>, + field: &Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + // `argument_to_field` will add a diagnostic directly, therefore unlike the other expanders we + // just create the synthetic dummy + let field = argument_to_field(expander, field).unwrap_or_else(|| Ident::synthetic(sym::dummy)); + + let mut value = mem::replace(&mut value.value, Expr::dummy()); + expander.visit(&mut value); + + if field.value == sym::dummy { + return Expr::dummy(); + } + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Field(FieldExpr { + id: NodeId::PLACEHOLDER, + span, + value: Box::new_in(value, expander.heap), + field, + }), + } +} + +/// Lowers a `.` call into a [`FieldExpr`]. +/// +/// Form: `(. value field)`. The field must be either a named identifier +/// or a non-negative integer literal (for positional tuple access). +/// +/// [`FieldExpr`]: crate::node::expr::FieldExpr +pub(super) fn lower_access<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_access(labeled_arguments)); + } + + if let [value, field] = &mut **arguments { + lower_access_impl(*span, expander, value, field) + } else { + expander + .diagnostics + .push(error::invalid_access_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/as.rs b/libs/@local/hashql/ast/src/lower/expander/as.rs new file mode 100644 index 00000000000..a92f4fe6dcc --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/as.rs @@ -0,0 +1,77 @@ +use core::mem; + +use hashql_core::{heap::BumpAllocator, module::Universe, span::SpanId}; + +use super::Expander; +use crate::{ + lower::expander::{error, r#type::lower_expr_to_type}, + node::{ + expr::{AsExpr, CallExpr, Expr, ExprKind, call::Argument}, + id::NodeId, + }, +}; + +fn lower_as_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + body: &mut Argument<'heap>, + r#type: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let mut body = mem::replace(&mut body.value, Expr::dummy()); + let mut r#type = mem::replace(&mut r#type.value, Expr::dummy()); + + expander.visit(&mut body); + expander.with_universe(Universe::Type, |expander| expander.visit(&mut r#type)); + let r#type = lower_expr_to_type(expander, r#type); + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::As(AsExpr { + id: NodeId::PLACEHOLDER, + span, + value: Box::new_in(body, expander.heap), + r#type: Box::new_in(r#type, expander.heap), + }), + } +} + +/// Lowers an `as` call into an [`AsExpr`]. +/// +/// Form: `(as value type)`. The type annotation is resolved in the type +/// universe. +/// +/// [`AsExpr`]: crate::node::expr::AsExpr +pub(super) fn lower_as<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_as(labeled_arguments)); + } + + if let [body, r#type] = &mut **arguments { + lower_as_impl(*span, expander, body, r#type) + } else { + expander + .diagnostics + .push(error::invalid_as_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/error.rs b/libs/@local/hashql/ast/src/lower/expander/error.rs new file mode 100644 index 00000000000..b6ec20e3eed --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/error.rs @@ -0,0 +1,2483 @@ +use alloc::borrow::Cow; +use core::fmt::{self, Display}; + +use hashql_core::{ + algorithms::did_you_mean, + module::{ + ModuleId, ModuleRegistry, Universe, + error::{KindSet, ResolutionError, ResolutionSuggestion}, + import::Import, + item::Item, + namespace::ModuleNamespace, + }, + span::{SpanId, Spanned}, + symbol::{Symbol, sym}, +}; +use hashql_diagnostics::{ + Diagnostic, DiagnosticIssues, Label, Patch, Suggestions, + category::{DiagnosticCategory, TerminalDiagnosticCategory}, + diagnostic::Message, + severity::Severity, +}; + +use crate::node::{ + expr::{ + ExprKind, + call::{Argument, LabeledArgument}, + }, + path::{Path, PathSegment, PathSegmentArgument}, +}; + +pub(crate) type ExpanderDiagnostic = Diagnostic; + +pub(crate) type ExpanderDiagnosticIssues = DiagnosticIssues; + +const EMPTY_PATH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "empty-path", + name: "Empty path", +}; + +const GENERIC_ARGUMENTS_IN_MODULE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "generic-arguments-in-module", + name: "Generic arguments in module path", +}; + +const UNRESOLVED_VARIABLE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "unresolved-variable", + name: "Unresolved variable", +}; + +const PACKAGE_NOT_FOUND: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "package-not-found", + name: "Package not found", +}; + +const MODULE_NOT_FOUND: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "module-not-found", + name: "Module not found", +}; + +const IMPORT_NOT_FOUND: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "import-not-found", + name: "Import not found", +}; + +const ITEM_NOT_FOUND: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "item-not-found", + name: "Item not found", +}; + +const MODULE_REQUIRED: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "module-required", + name: "Module required", +}; + +const AMBIGUOUS_NAME: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "ambiguous-name", + name: "Ambiguous name", +}; + +const EMPTY_MODULE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "empty-module", + name: "Empty module", +}; + +const INVALID_QUERY_LENGTH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-query-length", + name: "Invalid query length", +}; + +const INVALID_BINDING_NAME: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-binding-name", + name: "Invalid binding name", +}; + +const LABELED_ARGUMENTS_NOT_SUPPORTED: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "labeled-arguments-not-supported", + name: "Labeled arguments not supported", +}; + +const INVALID_ARGUMENT_COUNT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-argument-count", + name: "Invalid argument count", +}; + +const INVALID_TYPE_CONSTRUCTOR_CALL: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-type-constructor-call", + name: "Invalid type constructor call", +}; + +const TYPE_ANNOTATION_IN_TYPE_POSITION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "type-annotation-in-type-position", + name: "Type annotation in type position", +}; + +const INVALID_EXPRESSION_IN_TYPE_POSITION: TerminalDiagnosticCategory = + TerminalDiagnosticCategory { + id: "invalid-expression-in-type-position", + name: "Invalid expression in type position", + }; + +const FIELD_LITERAL_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "field-literal-type-annotation", + name: "Field literal with type annotation", +}; + +const INVALID_FIELD_LITERAL_TYPE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-field-literal-type", + name: "Invalid field literal type", +}; + +const FIELD_INDEX_OUT_OF_BOUNDS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "field-index-out-of-bounds", + name: "Field index out of bounds", +}; + +const INVALID_ACCESS_FIELD: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-access-field", + name: "Invalid access field", +}; + +const DUPLICATE_GENERIC_CONSTRAINT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "duplicate-generic-constraint", + name: "Duplicate generic constraint", +}; + +const INVALID_GENERIC_ARGUMENT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-generic-argument", + name: "Invalid generic argument", +}; + +const FN_GENERICS_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "fn-generics-type-annotation", + name: "Type annotation on generic parameter list", +}; + +const INVALID_FN_GENERICS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-fn-generics", + name: "Invalid generic parameter list", +}; + +const INVALID_FN_GENERIC_PARAM: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-fn-generic-param", + name: "Invalid generic parameter", +}; + +const FN_PARAMS_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "fn-params-type-annotation", + name: "Type annotation on parameter list", +}; + +const INVALID_FN_PARAMS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-fn-params", + name: "Invalid parameter list", +}; + +const DUPLICATE_FN_GENERIC: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "duplicate-fn-generic", + name: "Duplicate generic parameter", +}; + +const DUPLICATE_FN_PARAMETER: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "duplicate-fn-parameter", + name: "Duplicate function parameter", +}; + +const USE_IMPORTS_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "use-imports-type-annotation", + name: "Type annotation on use imports", +}; + +const INVALID_USE_IMPORTS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-use-imports", + name: "Invalid use imports", +}; + +const INVALID_USE_IMPORT_BINDING: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-use-import-binding", + name: "Invalid use import binding", +}; + +const INVALID_USE_ALIAS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-use-alias", + name: "Invalid use alias", +}; + +const DUPLICATE_USE_BINDING: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "duplicate-use-binding", + name: "Duplicate use binding", +}; + +const INTRINSIC_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "intrinsic-type-annotation", + name: "Type annotation on intrinsic binding", +}; + +const INTRINSIC_GENERIC_ARGUMENTS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "intrinsic-generic-arguments", + name: "Generic arguments on intrinsic", +}; + +const INVALID_USE_PATH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "invalid-use-path", + name: "Invalid use path", +}; + +const USE_PATH_GENERIC_ARGUMENTS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "use-path-generic-arguments", + name: "Generic arguments in use path", +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ExpanderDiagnosticCategory { + EmptyPath, + GenericArgumentsInModule, + UnresolvedVariable, + PackageNotFound, + ModuleNotFound, + ImportNotFound, + ItemNotFound, + ModuleRequired, + AmbiguousName, + EmptyModule, + InvalidQueryLength, + InvalidBindingName, + LabeledArgumentsNotSupported, + InvalidArgumentCount, + InvalidTypeConstructorCall, + TypeAnnotationInTypePosition, + InvalidExpressionInTypePosition, + FieldLiteralTypeAnnotation, + InvalidFieldLiteralType, + FieldIndexOutOfBounds, + InvalidAccessField, + DuplicateGenericConstraint, + InvalidGenericArgument, + FnGenericsTypeAnnotation, + InvalidFnGenerics, + InvalidFnGenericParam, + FnParamsTypeAnnotation, + InvalidFnParams, + DuplicateFnGeneric, + DuplicateFnParameter, + IntrinsicTypeAnnotation, + IntrinsicGenericArguments, + UseImportsTypeAnnotation, + InvalidUseImports, + InvalidUseImportBinding, + InvalidUseAlias, + DuplicateUseBinding, + InvalidUsePath, + UsePathGenericArguments, +} + +impl DiagnosticCategory for ExpanderDiagnosticCategory { + fn id(&self) -> Cow<'_, str> { + Cow::Borrowed("expander") + } + + fn name(&self) -> Cow<'_, str> { + Cow::Borrowed("Expander") + } + + fn subcategory(&self) -> Option<&dyn DiagnosticCategory> { + match self { + Self::EmptyPath => Some(&EMPTY_PATH), + Self::GenericArgumentsInModule => Some(&GENERIC_ARGUMENTS_IN_MODULE), + Self::UnresolvedVariable => Some(&UNRESOLVED_VARIABLE), + Self::PackageNotFound => Some(&PACKAGE_NOT_FOUND), + Self::ModuleNotFound => Some(&MODULE_NOT_FOUND), + Self::ImportNotFound => Some(&IMPORT_NOT_FOUND), + Self::ItemNotFound => Some(&ITEM_NOT_FOUND), + Self::ModuleRequired => Some(&MODULE_REQUIRED), + Self::AmbiguousName => Some(&AMBIGUOUS_NAME), + Self::EmptyModule => Some(&EMPTY_MODULE), + Self::InvalidQueryLength => Some(&INVALID_QUERY_LENGTH), + Self::InvalidBindingName => Some(&INVALID_BINDING_NAME), + Self::LabeledArgumentsNotSupported => Some(&LABELED_ARGUMENTS_NOT_SUPPORTED), + Self::InvalidArgumentCount => Some(&INVALID_ARGUMENT_COUNT), + Self::InvalidTypeConstructorCall => Some(&INVALID_TYPE_CONSTRUCTOR_CALL), + Self::TypeAnnotationInTypePosition => Some(&TYPE_ANNOTATION_IN_TYPE_POSITION), + Self::InvalidExpressionInTypePosition => Some(&INVALID_EXPRESSION_IN_TYPE_POSITION), + Self::FieldLiteralTypeAnnotation => Some(&FIELD_LITERAL_TYPE_ANNOTATION), + Self::InvalidFieldLiteralType => Some(&INVALID_FIELD_LITERAL_TYPE), + Self::FieldIndexOutOfBounds => Some(&FIELD_INDEX_OUT_OF_BOUNDS), + Self::InvalidAccessField => Some(&INVALID_ACCESS_FIELD), + Self::DuplicateGenericConstraint => Some(&DUPLICATE_GENERIC_CONSTRAINT), + Self::InvalidGenericArgument => Some(&INVALID_GENERIC_ARGUMENT), + Self::FnGenericsTypeAnnotation => Some(&FN_GENERICS_TYPE_ANNOTATION), + Self::InvalidFnGenerics => Some(&INVALID_FN_GENERICS), + Self::InvalidFnGenericParam => Some(&INVALID_FN_GENERIC_PARAM), + Self::FnParamsTypeAnnotation => Some(&FN_PARAMS_TYPE_ANNOTATION), + Self::InvalidFnParams => Some(&INVALID_FN_PARAMS), + Self::DuplicateFnGeneric => Some(&DUPLICATE_FN_GENERIC), + Self::DuplicateFnParameter => Some(&DUPLICATE_FN_PARAMETER), + Self::IntrinsicTypeAnnotation => Some(&INTRINSIC_TYPE_ANNOTATION), + Self::IntrinsicGenericArguments => Some(&INTRINSIC_GENERIC_ARGUMENTS), + Self::UseImportsTypeAnnotation => Some(&USE_IMPORTS_TYPE_ANNOTATION), + Self::InvalidUseImports => Some(&INVALID_USE_IMPORTS), + Self::InvalidUseImportBinding => Some(&INVALID_USE_IMPORT_BINDING), + Self::InvalidUseAlias => Some(&INVALID_USE_ALIAS), + Self::DuplicateUseBinding => Some(&DUPLICATE_USE_BINDING), + Self::InvalidUsePath => Some(&INVALID_USE_PATH), + Self::UsePathGenericArguments => Some(&USE_PATH_GENERIC_ARGUMENTS), + } + } +} + +/// Formats a user-written path prefix up to (and including) a given depth. +struct FormatUserPath<'a, 'heap> { + rooted: bool, + segments: &'a [PathSegment<'heap>], + up_to: Option, +} + +impl Display for FormatUserPath<'_, '_> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.rooted { + fmt.write_str("::")?; + } + + let segments = self + .up_to + .map_or(self.segments, |depth| &self.segments[..=depth]); + + for (index, segment) in segments.iter().enumerate() { + if index > 0 { + fmt.write_str("::")?; + } + + Display::fmt(&segment.name.value, fmt)?; + } + + Ok(()) + } +} + +/// Formats a canonical absolute path (always `::` prefixed) for an item in the registry. +fn format_absolute_path<'heap>(item: &Item<'heap>, registry: &ModuleRegistry<'heap>) -> String { + use core::iter; + + let path = item.absolute_path(registry).into_iter().map(Symbol::unwrap); + + iter::once("").chain(path).intersperse("::").collect() +} + +struct SpellingSuggestions<'heap, I> { + name: Spanned>, + candidates: I, + context: &'static str, + + top_n: usize = 5, + cutoff: Option = None, +} + +impl<'heap, I> SpellingSuggestions<'heap, I> { + fn emit(self, diagnostic: &mut ExpanderDiagnostic) -> Vec> + where + I: IntoIterator, IntoIter: Clone>, + { + let similar = did_you_mean( + self.name.value, + self.candidates + .into_iter() + .filter(|candidate| *candidate != sym::dummy), + Some(self.top_n), + self.cutoff, + ); + + let len = similar.len(); + + let patches: Vec<_> = similar + .iter() + .copied() + .map(|candidate| Patch::new(self.name.span, candidate.as_str().to_owned())) + .collect(); + + let Some(suggestions) = Suggestions::try_from_iter(patches) else { + debug_assert_eq!(len, 0); + return similar; + }; + + diagnostic.add_message(Message::help(self.context).with_suggestions(suggestions)); + similar + } +} + +#[coverage(off)] // An empty path should not be possible to be constructed inside the CST. +pub(crate) fn empty_path(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new(ExpanderDiagnosticCategory::EmptyPath, Severity::Bug) + .primary(Label::new(span, "path has no segments")); + + diagnostic.add_message(Message::note( + "after parsing and lowering, every path must contain at least one segment", + )); + + diagnostic +} + +pub(crate) fn generic_arguments_in_module( + module_segments: &[PathSegment<'_>], +) -> Option { + let mut arguments = module_segments + .iter() + .flat_map(|segment| &segment.arguments); + + let primary_span = arguments.next().map(PathSegmentArgument::span)?; + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::GenericArgumentsInModule, + Severity::Error, + ) + .primary(Label::new( + primary_span, + "generic argument not allowed here", + )); + + for argument in arguments { + diagnostic.add_label(Label::new(argument.span(), "neither is this")); + } + + diagnostic.add_message(Message::help( + "move the generic arguments to the final segment of the path, or remove them", + )); + + diagnostic.add_message(Message::note( + "modules are not generic; only the final item in a path can be parameterized", + )); + + Some(diagnostic) +} + +/// Converts a `ResolutionError` into an expander diagnostic. +pub(crate) fn from_resolution_error<'heap>( + path: &Path<'heap>, + namespace: &ModuleNamespace<'_, 'heap>, + universe: Universe, + error: ResolutionError<'heap>, +) -> ExpanderDiagnostic { + match error { + ResolutionError::InvalidQueryLength { expected } => invalid_query_length(path, expected), + ResolutionError::ModuleRequired { depth, found } => module_required(path, depth, found), + ResolutionError::PackageNotFound { + depth, + name, + suggestions, + } => package_not_found(path, depth, name, &suggestions), + ResolutionError::ModuleNotFound { + depth, + name, + suggestions, + } => module_not_found(path, depth, name, &suggestions), + ResolutionError::ImportNotFound { + depth, + name, + expected, + found, + suggestions, + } => { + if depth == 0 && path.segments.len() == 1 { + unresolved_variable(path, name, namespace, universe, found, &suggestions) + } else { + import_not_found(path, depth, name, expected, found, &suggestions) + } + } + ResolutionError::ItemNotFound { + depth, + name, + expected, + found, + suggestions, + } => item_not_found(path, depth, name, expected, found, &suggestions), + ResolutionError::Ambiguous(reference) => ambiguous_name(path, reference.name()), + ResolutionError::ModuleEmpty { depth } => empty_module(path, depth), + } +} + +/// Converts a `ResolutionError` from a `use` import into an expander diagnostic. +/// +/// For named imports, the resolution query extends beyond the path by one segment +/// (the binding name). When `depth` points past the path segments, `binding_name` +/// provides the span and name for the error. +pub(crate) fn from_import_resolution_error<'heap>( + path: &Path<'heap>, + binding_name: Option>>, + error: ResolutionError<'heap>, +) -> ExpanderDiagnostic { + match error { + ResolutionError::InvalidQueryLength { expected } => invalid_query_length(path, expected), + ResolutionError::ModuleRequired { depth, found } => module_required(path, depth, found), + ResolutionError::PackageNotFound { + depth, + name, + suggestions, + } => package_not_found(path, depth, name, &suggestions), + ResolutionError::ModuleNotFound { + depth, + name, + suggestions, + } => module_not_found(path, depth, name, &suggestions), + ResolutionError::ImportNotFound { + depth, + name, + expected, + found, + suggestions, + } => import_not_found(path, depth, name, expected, found, &suggestions), + ResolutionError::ItemNotFound { + depth, + name, + expected, + found, + suggestions, + } => { + // For named imports, the item (binding name) is beyond the path segments. + // Use the binding's span if available, otherwise fall back to the path span. + if depth >= path.segments.len() { + binding_name.map_or_else( + || { + item_not_found( + path, + depth.min(path.segments.len() - 1), + name, + expected, + found, + &suggestions, + ) + }, + |binding| { + item_not_found_at(binding.span, path, name, expected, found, &suggestions) + }, + ) + } else { + item_not_found(path, depth, name, expected, found, &suggestions) + } + } + ResolutionError::Ambiguous(reference) => ambiguous_name(path, reference.name()), + ResolutionError::ModuleEmpty { depth } => empty_module(path, depth), + } +} + +fn invalid_query_length(path: &Path<'_>, expected: usize) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidQueryLength, + Severity::Error, + ) + .primary(Label::new(path.span, "path is too short to resolve")); + + diagnostic.add_message(Message::help(format!( + "provide a path with at least {expected} segments, for example `module::item`", + ))); + + diagnostic +} + +fn module_required(path: &Path<'_>, depth: usize, found: Option) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + let user_path = FormatUserPath { + rooted: path.rooted, + segments: &path.segments, + up_to: Some(depth), + }; + + let primary_message = match found { + Some(Universe::Value) => format!("`{user_path}` is a value, not a module"), + Some(Universe::Type) => format!("`{user_path}` is a type, not a module"), + None => format!("`{user_path}` is not a module"), + }; + + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::ModuleRequired, Severity::Error) + .primary(Label::new(segment.name.span, primary_message)); + + if depth + 1 < path.segments.len() { + diagnostic.add_label(Label::new( + path.segments[depth + 1].name.span, + "cannot access items inside a non-module", + )); + } + + let help = match found { + Some(Universe::Value) => "if you meant to access a field, use `.` instead of `::`", + Some(Universe::Type) | None => "only modules can contain sub-items accessible via `::`", + }; + + diagnostic.add_message(Message::help(help)); + + diagnostic.add_message(Message::note( + "the `::` separator navigates into modules; values and types do not contain sub-items", + )); + + diagnostic +} + +fn package_not_found<'heap>( + path: &Path<'heap>, + depth: usize, + name: Symbol<'heap>, + suggestions: &[ResolutionSuggestion<'heap, ModuleId>], +) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::PackageNotFound, Severity::Error).primary( + Label::new(segment.name.span, format!("cannot find package `{name}`")), + ); + + diagnostic.add_label(Label::new(path.span, "in this path")); + + let emitted = SpellingSuggestions { + name: segment.name.symbol(), + candidates: suggestions.iter().map(|suggestion| suggestion.name), + context: "a package with a similar name exists", + .. + } + .emit(&mut diagnostic); + + if emitted.is_empty() { + diagnostic.add_message(Message::help( + "check the package name, or add it to the project dependencies", + )); + } + + diagnostic.add_message(Message::note( + "absolute paths start from an installed package", + )); + + diagnostic +} + +fn module_not_found<'heap>( + path: &Path<'heap>, + depth: usize, + name: Symbol<'heap>, + suggestions: &[ResolutionSuggestion<'heap, Item<'heap>>], +) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + let parent_path = FormatUserPath { + rooted: path.rooted, + segments: &path.segments, + up_to: depth.checked_sub(1), + }; + + let primary_message = if depth > 0 { + format!("cannot find module `{name}` in `{parent_path}`") + } else { + format!("cannot find module `{name}`") + }; + + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::ModuleNotFound, Severity::Error) + .primary(Label::new(segment.name.span, primary_message)); + + if depth > 0 { + diagnostic.add_label(Label::new( + path.segments[depth - 1].name.span, + "looked in this module", + )); + } + + let emitted = SpellingSuggestions { + name: segment.name.symbol(), + candidates: suggestions.iter().map(|suggestion| suggestion.name), + context: "a module with a similar name exists", + .. + } + .emit(&mut diagnostic); + + if emitted.is_empty() { + diagnostic.add_message(Message::help( + "check the module name and ensure it is exported from its parent", + )); + } + + diagnostic.add_message(Message::note( + "only exported sub-modules are reachable via `::`", + )); + + diagnostic +} + +fn import_not_found<'heap>( + path: &Path<'heap>, + depth: usize, + name: Symbol<'heap>, + expected: Option, + found: KindSet, + suggestions: &[ResolutionSuggestion<'heap, Import<'heap>>], +) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + + let kind_label = expected.map_or("name", universe_noun); + + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::ImportNotFound, Severity::Error).primary( + Label::new( + segment.name.span, + format!("{kind_label} `{name}` is not imported"), + ), + ); + + diagnostic.add_label(Label::new(path.span, "in this path")); + + emit_cross_universe_hint(&mut diagnostic, name, expected, found); + + let emitted = SpellingSuggestions { + name: segment.name.symbol(), + candidates: suggestions.iter().map(|suggestion| suggestion.name), + context: "a similar name is available", + .. + } + .emit(&mut diagnostic); + + if emitted.is_empty() && found.is_empty() { + diagnostic.add_message(Message::help( + "import the name with a `use` statement, or qualify it with a full path", + )); + } + + diagnostic +} + +/// Formats the universe as a noun for use in diagnostic messages. +const fn universe_noun(universe: Universe) -> &'static str { + match universe { + Universe::Value => "value", + Universe::Type => "type", + } +} + +/// Appends a cross-universe hint when the name exists but as a different kind. +/// +/// For example: `help: `Url` exists as a type, not a value`. +fn emit_cross_universe_hint( + diagnostic: &mut ExpanderDiagnostic, + name: Symbol<'_>, + expected: Option, + found: KindSet, +) { + if found.is_empty() { + return; + } + + // Remove the expected universe from the found set so we only report alternates. + let alternates = expected.map_or(found, |universe| found.without_universe(universe)); + + if alternates.is_empty() { + return; + } + + let message = expected.map_or_else( + || format!("`{name}` exists as {alternates}"), + |expected| { + format!( + "`{name}` exists as {alternates}, not {}", + universe_noun(expected) + ) + }, + ); + + diagnostic.add_message(Message::help(message)); +} + +fn item_not_found<'heap>( + path: &Path<'heap>, + depth: usize, + name: Symbol<'heap>, + expected: Option, + found: KindSet, + suggestions: &[ResolutionSuggestion<'heap, Item<'heap>>], +) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + + let kind_label = expected.map_or("item", universe_noun); + + let primary_message = if depth > 0 { + let parent_path = FormatUserPath { + rooted: path.rooted, + segments: &path.segments, + up_to: Some(depth - 1), + }; + + format!("cannot find {kind_label} `{name}` in module `{parent_path}`") + } else { + format!("cannot find {kind_label} `{name}` in this scope") + }; + + let mut diagnostic = Diagnostic::new(ExpanderDiagnosticCategory::ItemNotFound, Severity::Error) + .primary(Label::new(segment.name.span, primary_message)); + + if depth > 0 { + diagnostic.add_label(Label::new( + path.segments[depth - 1].name.span, + "looked in this module", + )); + } + + emit_cross_universe_hint(&mut diagnostic, name, expected, found); + + let emitted = SpellingSuggestions { + name: segment.name.symbol(), + candidates: suggestions.iter().map(|suggestion| suggestion.name), + context: "a similar item exists in this module", + .. + } + .emit(&mut diagnostic); + + if emitted.is_empty() && found.is_empty() { + diagnostic.add_message(Message::help( + "check the spelling and ensure the item is exported", + )); + } + + diagnostic +} + +/// Like `item_not_found`, but for items that are beyond the path's segments. +/// +/// Used by import resolution when the binding name (appended to the path query) +/// is the item that was not found. The primary label points at `span` instead of +/// indexing into `path.segments`. +fn item_not_found_at<'heap>( + span: SpanId, + path: &Path<'heap>, + name: Symbol<'heap>, + expected: Option, + found: KindSet, + suggestions: &[ResolutionSuggestion<'heap, Item<'heap>>], +) -> ExpanderDiagnostic { + let parent_path = FormatUserPath { + rooted: path.rooted, + segments: &path.segments, + up_to: path.segments.len().checked_sub(1), + }; + + let kind_label = expected.map_or("item", universe_noun); + + let mut diagnostic = Diagnostic::new(ExpanderDiagnosticCategory::ItemNotFound, Severity::Error) + .primary(Label::new( + span, + format!("cannot find {kind_label} `{name}` in module `{parent_path}`"), + )); + + if let Some(last) = path.segments.last() { + diagnostic.add_label(Label::new(last.name.span, "looked in this module")); + } + + emit_cross_universe_hint(&mut diagnostic, name, expected, found); + + let emitted = SpellingSuggestions { + name: Spanned { span, value: name }, + candidates: suggestions.iter().map(|suggestion| suggestion.name), + context: "a similar item exists in this module", + .. + } + .emit(&mut diagnostic); + + if emitted.is_empty() && found.is_empty() { + diagnostic.add_message(Message::help( + "check the spelling and ensure the item is exported", + )); + } + + diagnostic +} + +/// A single unqualified name could not be found in scope. +/// +/// This is the most common resolution failure. Suggestions are tiered: +/// +/// 1. **Local bindings**: names bound by `let`, `fn` parameters, etc. +/// 2. **Imported names**: names brought into scope by `use` +/// 3. **Registry items**: names available in the module registry but not yet imported +/// +/// Each tier produces Patches: tiers 1 and 2 replace the identifier with the +/// corrected spelling; tier 3 replaces it with the fully qualified path. +fn unresolved_variable<'heap>( + path: &Path<'heap>, + name: Symbol<'heap>, + namespace: &ModuleNamespace<'_, 'heap>, + universe: Universe, + found: KindSet, + import_suggestions: &[ResolutionSuggestion<'heap, Import<'heap>>], +) -> ExpanderDiagnostic { + let registry = namespace.registry(); + let locals = namespace.locals(universe); + let name_span = path.segments[0].name.span; + + let kind_label = universe_noun(universe); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::UnresolvedVariable, + Severity::Error, + ) + .primary(Label::new( + name_span, + format!("cannot find {kind_label} `{name}` in this scope"), + )); + + emit_cross_universe_hint(&mut diagnostic, name, Some(universe), found); + + let mut has_suggestions = false; + + // Suggest similar local bindings. + let emitted = SpellingSuggestions { + name: Spanned { + span: name_span, + value: name, + }, + candidates: locals.iter().copied(), + context: "a similar local binding exists", + .. + } + .emit(&mut diagnostic); + has_suggestions |= !emitted.is_empty(); + + // Suggest similar imported names, excluding those already covered by locals. + let import_candidates: Vec<_> = import_suggestions + .iter() + .map(|suggestion| suggestion.name) + .filter(|suggestion_name| !locals.contains(suggestion_name)) + .collect(); + + let emitted = SpellingSuggestions { + name: Spanned { + span: name_span, + value: name, + }, + candidates: import_candidates.iter().copied(), + context: "a similar imported name exists", + .. + } + .emit(&mut diagnostic); + has_suggestions |= !emitted.is_empty(); + + // Suggest fully qualified paths for items available elsewhere in the registry. + let importable: Vec<_> = registry + .search_by_name(name, universe) + .into_iter() + .collect(); + + if let Some(suggestions) = Suggestions::try_from_iter( + importable + .iter() + .take(5) + .map(|item| Patch::new(name_span, format_absolute_path(item, registry))), + ) { + has_suggestions = true; + diagnostic.add_message( + Message::help("use one of these fully qualified paths").with_suggestions(suggestions), + ); + } + + if let Some(first) = importable.first() { + let absolute = format_absolute_path(first, registry); + let example = if importable.len() > 1 { + format!("for example, bring it into scope: `use {absolute} in`") + } else { + format!("bring it into scope: `use {absolute} in`") + }; + diagnostic.add_message(Message::help(example)); + } + + let remaining = importable.len().saturating_sub(5); + if remaining > 0 { + diagnostic.add_message(Message::note(format!( + "{remaining} more items with this name exist in other modules", + ))); + } + + if !has_suggestions { + diagnostic.add_message(Message::help( + "check the spelling, or import the name with a `use` statement", + )); + } + + diagnostic.add_message(Message::note( + "this could be a typo, a name used outside its scope, or a missing declaration; if it is \ + a function or type from another module, you may need to import it first", + )); + + diagnostic +} + +fn ambiguous_name(path: &Path<'_>, name: Symbol<'_>) -> ExpanderDiagnostic { + let name_span = path + .segments + .iter() + .find_map(|segment| (segment.name.value == name).then_some(segment.name.span)) + .unwrap_or(path.span); + + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::AmbiguousName, Severity::Error).primary( + Label::new(name_span, format!("`{name}` refers to multiple items")), + ); + + diagnostic.add_label(Label::new(path.span, "in this path")); + + diagnostic.add_message(Message::help( + "use a fully qualified path to specify which item you mean", + )); + + diagnostic.add_message(Message::note( + "multiple items with this name are in scope; consider using explicit imports instead of \ + globs to prevent conflicts", + )); + + diagnostic +} + +fn empty_module(path: &Path<'_>, depth: usize) -> ExpanderDiagnostic { + let segment = &path.segments[depth]; + let user_path = FormatUserPath { + rooted: path.rooted, + segments: &path.segments, + up_to: Some(depth), + }; + + let mut diagnostic = Diagnostic::new(ExpanderDiagnosticCategory::EmptyModule, Severity::Error) + .primary(Label::new( + segment.name.span, + format!("module `{user_path}` has no exported items"), + )); + + diagnostic.add_label(Label::new(path.span, "in this path")); + + diagnostic.add_message(Message::help( + "check that the module path is correct, or import a specific item instead of the module", + )); + + diagnostic +} + +/// The first argument to `let` is not a simple identifier. +/// +/// The name position requires a plain name like `x` or `count`, not a qualified +/// path, generic expression, or arbitrary expression. +pub(crate) fn invalid_let_binding_name(name: &Argument<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidBindingName, + Severity::Error, + ) + .primary(Label::new( + name.value.span, + "expected a simple identifier for the `let` binding name", + )); + + diagnostic.add_message(Message::help( + "write `(let name value body)` with a plain name such as `x`", + )); + + diagnostic.add_message(Message::note( + "the first argument to `let` introduces a new local binding and must be a plain \ + identifier without path qualification or generic arguments", + )); + + diagnostic +} + +/// A `let` call was passed labeled arguments. +/// +/// `let` only accepts positional arguments in a fixed order. +pub(crate) fn labeled_arguments_in_let( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `let`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(let name value body)` or `(let name type value body)`", + )); + + diagnostic.add_message(Message::note( + "`let` has a fixed argument order: name, optional type annotation, value, body", + )); + + diagnostic +} + +/// A `let` call was passed the wrong number of arguments. +/// +/// `let` accepts either 3 arguments `(let name value body)` or 4 arguments +/// `(let name type value body)`. +pub(crate) fn invalid_let_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 3 or 4 arguments to `let`, found {count}"), + )); + + for argument in arguments.iter().skip(4) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help( + "use `(let name value body)` or `(let name type value body)`", + )); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the binding name, an optional type annotation, the bound \ + value, and the body expression where the name is in scope", + )); + + diagnostic +} + +/// A `let` binding with a type annotation resolved to a compiler intrinsic. +/// +/// Intrinsics are not ordinary values and cannot be narrowed by a type +/// annotation. The binding is still valid without the annotation. +pub(crate) fn intrinsic_type_annotation( + annotation_span: SpanId, + value_span: SpanId, + name: Symbol<'_>, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::IntrinsicTypeAnnotation, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + format!("type annotation on `{name}` is not allowed here"), + )); + + diagnostic.add_label(Label::new( + value_span, + "this resolves to a compiler intrinsic", + )); + + diagnostic.add_message(Message::help(format!( + "remove the type annotation: `(let {name} value body)`", + ))); + + diagnostic.add_message(Message::note( + "compiler intrinsics cannot be given a type annotation because they are not ordinary \ + values", + )); + + diagnostic +} + +/// Generic arguments were attached to an intrinsic that does not accept them. +/// +/// Special forms and built-in type operators are not generic. The generic +/// arguments should be removed. +pub(crate) fn intrinsic_generic_arguments(ident: &PathSegment<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::IntrinsicGenericArguments, + Severity::Error, + ) + .primary(Label::new( + ident.span, + format!("`{}` does not accept generic arguments", ident.name.value), + )); + + diagnostic.add_message(Message::help(format!( + "remove the generic arguments from `{}`", + ident.name.value, + ))); + + diagnostic +} + +/// An `as` call was passed labeled arguments. +/// +/// `as` only accepts positional arguments. +pub(crate) fn labeled_arguments_in_as( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `as`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(as value type)`", + )); + + diagnostic +} + +/// An `as` call was passed the wrong number of arguments. +/// +/// `as` accepts exactly 2 arguments: `(as value Type)`. +pub(crate) fn invalid_as_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 2 arguments to `as`, found {count}"), + )); + + for argument in arguments.iter().skip(2) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(as value type)`")); + + diagnostic.add_message(Message::note( + "the first argument is the value to cast and the second is the target type", + )); + + diagnostic +} + +/// An `if` call was passed labeled arguments. +/// +/// `if` only accepts positional arguments. +pub(crate) fn labeled_arguments_in_if( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `if`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(if condition then)` or `(if condition then else)`", + )); + + diagnostic +} + +/// An `if` call was passed the wrong number of arguments. +/// +/// `if` accepts either 2 arguments `(if condition then)` or 3 arguments +/// `(if condition then else)`. +pub(crate) fn invalid_if_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 2 or 3 arguments to `if`, found {count}"), + )); + + for argument in arguments.iter().skip(3) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help( + "use `(if condition then)` or `(if condition then else)`", + )); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the condition, the then branch, and an optional else branch", + )); + + diagnostic +} + +/// An `input` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_input( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `input`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(input name type)` or `(input name type default)`", + )); + + diagnostic +} + +/// An `input` call was passed the wrong number of arguments. +/// +/// `input` accepts either 2 arguments `(input name Type)` or 3 arguments +/// `(input name Type default)`. +pub(crate) fn invalid_input_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 2 or 3 arguments to `input`, found {count}"), + )); + + for argument in arguments.iter().skip(3) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help( + "use `(input name type)` or `(input name type default)`", + )); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the input name, its type, and an optional default value", + )); + + diagnostic +} + +/// The first argument to `input` is not a simple identifier. +pub(crate) fn invalid_input_binding_name(name: &Argument<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidBindingName, + Severity::Error, + ) + .primary(Label::new( + name.value.span, + "expected a simple identifier for the `input` name", + )); + + diagnostic.add_message(Message::help( + "write `(input name type)` with a plain name such as `user_id`", + )); + + diagnostic.add_message(Message::note( + "the first argument to `input` declares a named parameter and must be a plain identifier", + )); + + diagnostic +} + +/// An `index` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_index( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `[]`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `([] collection index)`", + )); + + diagnostic +} + +/// An `index` call was passed the wrong number of arguments. +/// +/// `index` accepts exactly 2 arguments: `([] collection index)`. +pub(crate) fn invalid_index_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 2 arguments to `[]`, found {count}"), + )); + + for argument in arguments.iter().skip(2) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `([] collection index)`")); + + diagnostic.add_message(Message::note( + "the first argument is the collection and the second is the index", + )); + + diagnostic +} + +/// An `access` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_access( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `.`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(. value field)`", + )); + + diagnostic +} + +/// An `access` call was passed the wrong number of arguments. +/// +/// `access` accepts exactly 2 arguments: `(. value field)`. +pub(crate) fn invalid_access_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 2 arguments to `.`, found {count}"), + )); + + for argument in arguments.iter().skip(2) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(. value field)`")); + + diagnostic.add_message(Message::note( + "the first argument is the value and the second is the field name or index", + )); + + diagnostic +} + +/// The field argument to `access` is not a valid field identifier or integer index. +pub(crate) fn invalid_access_field(argument: &Argument<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidAccessField, + Severity::Error, + ) + .primary(Label::new( + argument.value.span, + "expected a field name or integer index", + )); + + diagnostic.add_message(Message::help( + "use a simple identifier like `name` or an integer like `0` for tuple fields", + )); + + diagnostic +} + +/// A numeric field literal in `access` has a type annotation. +pub(crate) fn field_literal_type_annotation(annotation_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::FieldLiteralTypeAnnotation, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + "type annotations are not allowed on field index literals", + )); + + diagnostic.add_message(Message::help( + "remove the type annotation and use a plain integer like `0`", + )); + + diagnostic +} + +/// A field literal in `access` is not an integer. +pub(crate) fn invalid_field_literal_type(literal_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidFieldLiteralType, + Severity::Error, + ) + .primary(Label::new( + literal_span, + "expected an integer for field indexing", + )); + + diagnostic.add_message(Message::help( + "use an integer index like `0` for tuple field access, or a name like `field` for named \ + field access", + )); + + diagnostic +} + +/// A field index literal is too large. +pub(crate) fn field_index_out_of_bounds(literal_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::FieldIndexOutOfBounds, + Severity::Error, + ) + .primary(Label::new(literal_span, "field index is out of bounds")); + + diagnostic.add_message(Message::help( + "use a non-negative integer that fits within platform bounds", + )); + + diagnostic +} + +/// A `type` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_type( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `type`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(type Name type-expr body)`", + )); + + diagnostic +} + +/// A `type` call was passed the wrong number of arguments. +/// +/// `type` accepts exactly 3 arguments: `(type Name type-expr body)`. +pub(crate) fn invalid_type_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 3 arguments to `type`, found {count}"), + )); + + for argument in arguments.iter().skip(3) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(type Name type-expr body)`")); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the type name (optionally with generic parameters), the \ + type definition, and the body where the name is in scope", + )); + + diagnostic +} + +/// The first argument to `type` is not a valid type name. +/// +/// The name position requires a simple identifier, optionally with generic +/// parameters like `Foo`, not a qualified path or arbitrary expression. +pub(crate) fn invalid_type_binding_name(name: &Argument<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidBindingName, + Severity::Error, + ) + .primary(Label::new( + name.value.span, + "expected a type name for the `type` binding", + )); + + diagnostic.add_message(Message::help( + "write `(type Name type-expr body)` with a name like `MyType` or `Pair`", + )); + + diagnostic.add_message(Message::note( + "the first argument to `type` introduces a new type alias and must be a simple \ + identifier, optionally with generic parameters", + )); + + diagnostic +} + +/// The first argument to `newtype` is not a valid type name. +pub(crate) fn invalid_newtype_binding_name(name: &Argument<'_>) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidBindingName, + Severity::Error, + ) + .primary(Label::new( + name.value.span, + "expected a type name for the `newtype` binding", + )); + + diagnostic.add_message(Message::help( + "write `(newtype Name type-expr body)` with a name like `UserId` or `Pair`", + )); + + diagnostic.add_message(Message::note( + "the first argument to `newtype` introduces a new distinct type and must be a simple \ + identifier, optionally with generic parameters", + )); + + diagnostic +} + +/// A `newtype` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_newtype( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `newtype`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(newtype Name type-expr body)`", + )); + + diagnostic +} + +/// A `newtype` call was passed the wrong number of arguments. +/// +/// `newtype` accepts exactly 3 arguments: `(newtype Name type-expr body)`. +pub(crate) fn invalid_newtype_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 3 arguments to `newtype`, found {count}"), + )); + + for argument in arguments.iter().skip(3) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(newtype Name type-expr body)`")); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the type name (optionally with generic parameters), the \ + underlying type, and the body where the name is in scope", + )); + + diagnostic +} + +/// A generic parameter was declared more than once. +pub(crate) fn duplicate_generic_constraint( + duplicate_span: SpanId, + name: Symbol<'_>, + original_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::DuplicateGenericConstraint, + Severity::Error, + ) + .primary(Label::new( + duplicate_span, + format!("duplicate generic parameter `{name}`"), + )); + + diagnostic.add_label(Label::new( + original_span, + format!("`{name}` was first declared here"), + )); + + diagnostic.add_message(Message::help( + "remove the duplicate declaration or use a different name", + )); + + diagnostic +} + +/// A generic argument in a type name is not a valid constraint. +/// +/// Generic arguments must be simple identifiers (for unconstrained parameters) +/// or named constraints like `T: Bound`. +pub(crate) fn invalid_generic_argument(span: SpanId, is_path: bool) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidGenericArgument, + Severity::Error, + ) + .primary(Label::new(span, "expected a simple type parameter")); + + if is_path { + diagnostic.add_message(Message::help( + "use a simple identifier like `T`, not a qualified path", + )); + } else { + diagnostic.add_message(Message::help( + "use a simple identifier like `T` or a constraint like `T: Bound`", + )); + } + + diagnostic.add_message(Message::note( + "generic parameters are declared as `Name` for unconstrained or `Name` for \ + constrained parameters", + )); + + diagnostic +} + +/// A type constructor call was passed labeled arguments. +/// +/// Type constructor calls like `(| Int String)` only accept positional operands. +pub(crate) fn labeled_arguments_in_type_call( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in type constructor calls", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "remove the labels and write the operand types positionally, for example `(| Int String)`", + )); + + diagnostic.add_message(Message::note( + "type constructors such as `|` (union) and `&` (intersection) take positional type \ + operands", + )); + + diagnostic +} + +/// A call expression in type position resolved to something that is not a +/// type constructor. +/// +/// Only type constructors like `|` (union) and `&` (intersection) use call +/// syntax in type position. Other types are referenced directly by path. +pub(crate) fn invalid_type_constructor_call( + function_span: SpanId, + call_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidTypeConstructorCall, + Severity::Error, + ) + .primary(Label::new( + function_span, + "cannot use this as a type constructor", + )); + + diagnostic.add_label(Label::new( + call_span, + "this call appears in a type position", + )); + + diagnostic.add_message(Message::help( + "use a type path directly, or use `(| ...)` / `(& ...)` for unions and intersections", + )); + + diagnostic.add_message(Message::note( + "generic type arguments belong on the type path itself, for example `Foo`, not `(Foo \ + T)`", + )); + + diagnostic +} + +/// A type was called like a function in type position, but types are not +/// callable type constructors. +/// +/// The user wrote something like `(String Int)` when they probably meant +/// `String` for generics or just `String` as a plain type reference. +pub(crate) fn type_is_not_callable( + name: Symbol<'_>, + function_span: SpanId, + call_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidTypeConstructorCall, + Severity::Error, + ) + .primary(Label::new( + function_span, + format!("`{name}` is a type, not a type constructor"), + )); + + diagnostic.add_label(Label::new( + call_span, + "call syntax is only valid for type constructors like `|` and `&`", + )); + + diagnostic.add_message(Message::help(format!( + "use `{name}` directly as a type path, or write `{name}<...>` for generic arguments", + ))); + + diagnostic.add_message(Message::note( + "only `|` (union) and `&` (intersection) can be called as type constructors in type \ + position", + )); + + diagnostic +} + +/// Whether the aggregate expression is a tuple or a struct. +#[derive(Debug, Copy, Clone)] +pub(crate) enum AggregateKind { + Tuple, + Struct, +} + +impl Display for AggregateKind { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Tuple => fmt.write_str("tuple"), + Self::Struct => fmt.write_str("struct"), + } + } +} + +/// A tuple or struct expression in type position has a redundant type annotation. +/// +/// When used as a type, `(Int, String)` already defines a tuple type and +/// `(name: String)` already defines a struct type. Adding an annotation is +/// contradictory. +pub(crate) fn type_annotation_in_type_position( + annotation_span: SpanId, + expr_span: SpanId, + kind: AggregateKind, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::TypeAnnotationInTypePosition, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + "type annotations are not allowed inside a type expression", + )); + + diagnostic.add_label(Label::new( + expr_span, + format!("this {kind} is already being used as a type"), + )); + + diagnostic.add_message(Message::help("remove the type annotation")); + + let note = match kind { + AggregateKind::Tuple => "in type position, `(Int, String)` already defines a tuple type", + AggregateKind::Struct => "in type position, `(name: String)` already defines a struct type", + }; + + diagnostic.add_message(Message::note(note)); + + diagnostic +} + +const VALID_TYPE_FORMS: &str = "valid type forms are: type paths like `String`, tuple types like \ + `(Int, String)`, struct types like `(name: String)`, `_` for \ + inference, unions `(| ...)`, and intersections `(& ...)`"; + +/// An expression that cannot be used as a type was found in type position. +/// +/// Type expressions are a restricted subset of general expressions. Paths, +/// tuples, structs, `_`, unions, and intersections are the valid forms. +/// +/// Returns `None` for `Dummy` expressions, which are recovery sentinels from +/// earlier errors. +pub(crate) fn invalid_expression_in_type_position( + span: SpanId, + kind: &ExprKind<'_>, +) -> Option { + let description = match kind { + ExprKind::Dict(_) => "a dictionary expression", + ExprKind::List(_) => "a list expression", + ExprKind::Literal(_) => "a literal value", + ExprKind::Let(_) => "a `let` expression", + ExprKind::Type(_) => "a `type` declaration", + ExprKind::NewType(_) => "a `newtype` declaration", + ExprKind::Input(_) => "an `input` expression", + ExprKind::Closure(_) => "a closure expression", + ExprKind::If(_) => "an `if` expression", + ExprKind::Field(_) => "a field access expression", + ExprKind::Index(_) => "an index expression", + ExprKind::As(_) => "an `as` expression", + ExprKind::Dummy => return None, + ExprKind::Call(_) + | ExprKind::Tuple(_) + | ExprKind::Struct(_) + | ExprKind::Path(_) + | ExprKind::Underscore => { + unreachable!("these expression kinds are valid in type position") + } + }; + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidExpressionInTypePosition, + Severity::Error, + ) + .primary(Label::new( + span, + format!("cannot use {description} as a type"), + )); + + diagnostic.add_message(Message::help( + "replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)`", + )); + + diagnostic.add_message(Message::note(VALID_TYPE_FORMS)); + + Some(diagnostic) +} + +/// A `fn` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_fn( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `fn`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(fn generics params return-type body)`", + )); + + diagnostic +} + +/// A `fn` call was passed the wrong number of arguments. +/// +/// `fn` accepts exactly 4 arguments: `(fn generics params return-type body)`. +pub(crate) fn invalid_fn_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 4 arguments to `fn`, found {count}"), + )); + + for argument in arguments.iter().skip(4) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(fn generics params return-type body)`")); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the generic parameters (a tuple or struct), the function \ + parameters (a struct), the return type, and the body expression", + )); + + diagnostic +} + +/// The generic parameter list of a `fn` has a type annotation. +/// +/// The generics argument to `fn` is a tuple or struct that declares type +/// variables. Annotating it with a type (e.g., `(T, U): something`) is not +/// meaningful. +pub(crate) fn fn_generics_type_annotation(annotation_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::FnGenericsTypeAnnotation, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + "type annotation is not allowed on the generic parameter list", + )); + + diagnostic.add_message(Message::help( + "remove the type annotation; the generic parameter list declares type variables, not a \ + typed value", + )); + + diagnostic.add_message(Message::note( + "write `(fn (T, U) ...)` for unbounded generics or `(fn (T: bound, U: _) ...)` for \ + bounded generics", + )); + + diagnostic +} + +/// The generics argument to `fn` is not a valid form. +/// +/// Generic parameters must be specified as a tuple of identifiers (unbounded) +/// or a struct with optional bounds. +pub(crate) fn invalid_fn_generics(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidFnGenerics, + Severity::Error, + ) + .primary(Label::new( + span, + "expected a tuple or struct for generic parameters", + )); + + diagnostic.add_message(Message::help( + "use a tuple like `(T, U)` for unbounded generics or a struct like `(T: bound, U: _)` for \ + bounded generics", + )); + + diagnostic +} + +/// A generic parameter in a `fn` tuple is not a simple identifier. +/// +/// Each element of the generics tuple must be a plain name like `T`, +/// not a qualified path or complex expression. +pub(crate) fn invalid_fn_generic_param(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidFnGenericParam, + Severity::Error, + ) + .primary(Label::new(span, "expected a simple type parameter name")); + + diagnostic.add_message(Message::help( + "each generic parameter must be a plain identifier like `T`, not a qualified path or \ + expression", + )); + + diagnostic +} + +/// The parameter list of a `fn` has a type annotation. +/// +/// The params argument to `fn` is a struct where each field declares a +/// parameter and its type. Annotating the struct itself is not meaningful. +pub(crate) fn fn_params_type_annotation(annotation_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::FnParamsTypeAnnotation, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + "type annotation is not allowed on the parameter list", + )); + + diagnostic.add_message(Message::help( + "remove the type annotation; each parameter already declares its type individually as \ + `(name: type)`", + )); + + diagnostic +} + +/// The params argument to `fn` is not a struct expression. +/// +/// Function parameters must be declared as a struct where each field is a +/// parameter with its type. +pub(crate) fn invalid_fn_params(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::InvalidFnParams, Severity::Error).primary( + Label::new(span, "expected a struct for function parameters"), + ); + + diagnostic.add_message(Message::help( + "write parameters as `(x: int, y: string)` where each field declares a parameter and its \ + type", + )); + + diagnostic +} + +/// A generic parameter name was declared more than once in a `fn` form. +pub(crate) fn duplicate_fn_generic( + duplicate_span: SpanId, + name: Symbol<'_>, + original_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::DuplicateFnGeneric, + Severity::Error, + ) + .primary(Label::new( + duplicate_span, + format!("duplicate generic parameter `{name}`"), + )); + + diagnostic.add_label(Label::new( + original_span, + format!("`{name}` was first declared here"), + )); + + diagnostic.add_message(Message::help( + "remove the duplicate declaration or use a different name", + )); + + diagnostic +} + +/// A function parameter name was declared more than once in a `fn` form. +pub(crate) fn duplicate_fn_parameter( + duplicate_span: SpanId, + name: Symbol<'_>, + original_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::DuplicateFnParameter, + Severity::Error, + ) + .primary(Label::new( + duplicate_span, + format!("duplicate parameter `{name}`"), + )); + + diagnostic.add_label(Label::new( + original_span, + format!("`{name}` was first declared here"), + )); + + diagnostic.add_message(Message::help( + "remove the duplicate parameter or use a different name", + )); + + diagnostic +} + +/// A `use` call was passed labeled arguments. +pub(crate) fn labeled_arguments_in_use( + labeled_arguments: &[LabeledArgument<'_>], +) -> ExpanderDiagnostic { + let (first, rest) = labeled_arguments + .split_first() + .expect("caller should check that labeled_arguments is non-empty"); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::LabeledArgumentsNotSupported, + Severity::Error, + ) + .primary(Label::new( + first.span, + "labeled arguments are not allowed in `use`", + )); + + for argument in rest { + diagnostic.add_label(Label::new( + argument.span, + "labeled argument not allowed here", + )); + } + + diagnostic.add_message(Message::help( + "pass the arguments positionally: `(use path imports body)`", + )); + + diagnostic +} + +/// A `use` call was passed the wrong number of arguments. +pub(crate) fn invalid_use_argument_count( + call_span: SpanId, + arguments: &[Argument<'_>], +) -> ExpanderDiagnostic { + let count = arguments.len(); + + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidArgumentCount, + Severity::Error, + ) + .primary(Label::new( + call_span, + format!("expected 3 arguments to `use`, found {count}"), + )); + + for argument in arguments.iter().skip(3) { + diagnostic.add_label(Label::new(argument.span, "unexpected argument")); + } + + diagnostic.add_message(Message::help("use `(use path imports body)`")); + + diagnostic.add_message(Message::note( + "the arguments are, in order: the module path to import from, the import specifier (`*`, \ + a tuple of names, or a struct of aliases), and the body where the imports are in scope", + )); + + diagnostic +} + +/// The import list in a `use` has a type annotation. +pub(crate) fn use_imports_type_annotation(annotation_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::UseImportsTypeAnnotation, + Severity::Error, + ) + .primary(Label::new( + annotation_span, + "type annotations are not allowed on the import list", + )); + + diagnostic.add_message(Message::help( + "remove the type annotation; the import list declares which names to bring into scope, \ + not a typed value", + )); + + diagnostic +} + +/// The imports argument to `use` is not a valid form. +pub(crate) fn invalid_use_imports(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidUseImports, + Severity::Error, + ) + .primary(Label::new( + span, + "expected `*`, a tuple of names, or a struct of aliases", + )); + + diagnostic.add_message(Message::help( + "use `*` to import everything, `(a, b)` to import specific names, or `(a: alias, b: _)` \ + to import with aliases", + )); + + diagnostic +} + +/// An import binding in a `use` tuple is not a simple identifier. +pub(crate) fn invalid_use_import_binding(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::InvalidUseImportBinding, + Severity::Error, + ) + .primary(Label::new(span, "expected a simple name to import")); + + diagnostic.add_message(Message::help( + "each import must be a plain identifier like `foo`, not a qualified path or expression", + )); + + diagnostic +} + +/// An alias in a `use` struct binding is not a valid form. +/// +/// Aliases must be either `_` (keep the original name) or a simple identifier. +pub(crate) fn invalid_use_alias(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::InvalidUseAlias, Severity::Error).primary( + Label::new(span, "expected `_` or a simple identifier as the alias"), + ); + + diagnostic.add_message(Message::help( + "use `_` to keep the original name or a simple identifier to rename: `(name: alias)` or \ + `(name: _)`", + )); + + diagnostic +} + +/// The same effective name was bound twice in a `use` import list. +pub(crate) fn duplicate_use_binding( + duplicate_span: SpanId, + name: Symbol<'_>, + original_span: SpanId, +) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::DuplicateUseBinding, + Severity::Error, + ) + .primary(Label::new( + duplicate_span, + format!("duplicate import binding `{name}`"), + )); + + diagnostic.add_label(Label::new( + original_span, + format!("`{name}` was first imported here"), + )); + + diagnostic.add_message(Message::help( + "remove the duplicate or use an alias to import under a different name", + )); + + diagnostic +} + +/// The path argument to `use` is not a path expression. +pub(crate) fn invalid_use_path(span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = + Diagnostic::new(ExpanderDiagnosticCategory::InvalidUsePath, Severity::Error) + .primary(Label::new(span, "expected a module path")); + + diagnostic.add_message(Message::help( + "the first argument to `use` must be a path like `core::math` identifying the module to \ + import from", + )); + + diagnostic +} + +/// The path in a `use` statement contains generic arguments. +pub(crate) fn use_path_generic_arguments(path_span: SpanId) -> ExpanderDiagnostic { + let mut diagnostic = Diagnostic::new( + ExpanderDiagnosticCategory::UsePathGenericArguments, + Severity::Error, + ) + .primary(Label::new( + path_span, + "generic arguments are not allowed in `use` paths", + )); + + diagnostic.add_message(Message::help( + "remove the generic arguments; `use` imports modules and items, which do not take type \ + parameters at the import site", + )); + + diagnostic +} diff --git a/libs/@local/hashql/ast/src/lower/expander/fn.rs b/libs/@local/hashql/ast/src/lower/expander/fn.rs new file mode 100644 index 00000000000..5073b1c60fa --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/fn.rs @@ -0,0 +1,342 @@ +use core::mem; + +use hashql_core::{ + collections::fast_hash_map_with_capacity_in, + heap::{self, BumpAllocator}, + module::Universe, + span::SpanId, +}; + +use super::{Expander, r#let::expr_to_ident, r#type::lower_expr_to_type}; +use crate::{ + lower::expander::error, + node::{ + expr::{ + CallExpr, ClosureExpr, Expr, ExprKind, + call::Argument, + closure::{ClosureParam, ClosureSignature}, + }, + generic::{GenericParam, Generics}, + id::NodeId, + }, +}; + +fn lower_generics_tuple<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + tuple: &mut crate::node::expr::TupleExpr<'heap>, +) -> Generics<'heap> { + if let Some(annotation) = tuple.r#type.as_ref() { + expander + .diagnostics + .push(error::fn_generics_type_annotation(annotation.span)); + } + + let mut params = Vec::with_capacity_in(tuple.elements.len(), expander.heap); + + for element in &mut tuple.elements { + let Some(ident) = expr_to_ident(&element.value) else { + expander + .diagnostics + .push(error::invalid_fn_generic_param(element.value.span)); + + continue; + }; + + params.push(GenericParam { + id: NodeId::PLACEHOLDER, + span: element.span, + name: ident, + bound: None, + }); + } + + Generics { + id: NodeId::PLACEHOLDER, + span: tuple.span, + params, + } +} + +fn lower_generics_struct<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + r#struct: &mut crate::node::expr::StructExpr<'heap>, +) -> Generics<'heap> +where + S: BumpAllocator, +{ + if let Some(annotation) = r#struct.r#type.as_ref() { + expander + .diagnostics + .push(error::fn_generics_type_annotation(annotation.span)); + } + + let mut params = Vec::with_capacity_in(r#struct.entries.len(), expander.heap); + + expander.with_universe(Universe::Type, |expander| { + expander.bind_many_with( + &mut r#struct.entries, + |entries, binder| { + for entry in &**entries { + binder.bind(entry.key.value, Universe::Type); + } + }, + |expander, entries| { + for mut entry in entries.drain(..) { + expander.visit(&mut entry.value); + let bound = lower_expr_to_type(expander, entry.value); + + params.push(GenericParam { + id: NodeId::PLACEHOLDER, + span: entry.span, + name: entry.key, + bound: Some(bound).filter(|bound| { + !matches!(bound.kind, crate::node::r#type::TypeKind::Infer) + }), + }); + } + }, + ) + }); + + Generics { + id: NodeId::PLACEHOLDER, + span: r#struct.span, + params, + } +} + +fn lower_generics<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + + generics: &mut Argument<'heap>, +) -> Generics<'heap> +where + S: BumpAllocator, +{ + let mut generics = match &mut generics.value.kind { + ExprKind::Tuple(tuple) => lower_generics_tuple(expander, tuple), + ExprKind::Struct(r#struct) => lower_generics_struct(expander, r#struct), + ExprKind::Call(_) + | ExprKind::Dict(_) + | ExprKind::List(_) + | ExprKind::Literal(_) + | ExprKind::Path(_) + | ExprKind::Let(_) + | ExprKind::Type(_) + | ExprKind::NewType(_) + | ExprKind::Input(_) + | ExprKind::Closure(_) + | ExprKind::If(_) + | ExprKind::Field(_) + | ExprKind::Index(_) + | ExprKind::As(_) + | ExprKind::Underscore + | ExprKind::Dummy => { + expander + .diagnostics + .push(error::invalid_fn_generics(generics.value.span)); + + Generics { + id: NodeId::PLACEHOLDER, + span: generics.value.span, + params: heap::Vec::new_in(expander.heap), + } + } + }; + + { + let diagnostics = &mut expander.diagnostics; + expander.scratch.scoped(|scratch| { + let mut seen = fast_hash_map_with_capacity_in(generics.params.len(), scratch); + + generics.params.retain(|param| { + if let Err(error) = seen.try_insert(param.name.value, param.span) { + diagnostics.push(error::duplicate_fn_generic( + param.span, + param.name.value, + *error.entry.get(), + )); + return false; + } + + true + }); + }); + } + + generics +} + +fn lower_params<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + + params: &mut Argument<'heap>, +) -> heap::Vec<'heap, ClosureParam<'heap>> +where + S: BumpAllocator, +{ + let ExprKind::Struct(r#struct) = &mut params.value.kind else { + expander + .diagnostics + .push(error::invalid_fn_params(params.value.span)); + + return heap::Vec::new_in(expander.heap); + }; + + if let Some(annotation) = r#struct.r#type.as_ref() { + expander + .diagnostics + .push(error::fn_params_type_annotation(annotation.span)); + } + + let mut params = Vec::with_capacity_in(r#struct.entries.len(), expander.heap); + + expander.with_universe(Universe::Type, |expander| { + for mut entry in r#struct.entries.drain(..) { + expander.visit(&mut entry.value); + let bound = lower_expr_to_type(expander, entry.value); + + params.push(ClosureParam { + id: NodeId::PLACEHOLDER, + span: entry.span, + name: entry.key, + bound, + }); + } + }); + + { + let diagnostics = &mut expander.diagnostics; + expander.scratch.scoped(|scratch| { + let mut seen = fast_hash_map_with_capacity_in(params.len(), scratch); + + params.retain(|param| { + if let Err(error) = seen.try_insert(param.name.value, param.span) { + diagnostics.push(error::duplicate_fn_parameter( + param.span, + param.name.value, + *error.entry.get(), + )); + return false; + } + + true + }); + }); + } + + params +} + +fn lower_fn_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + generics: &mut Argument<'heap>, + params: &mut Argument<'heap>, + r#return: &mut Argument<'heap>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let generics = lower_generics(expander, generics); + + let (params, returns, body) = expander.bind_many( + generics + .params + .iter() + .map(|param| (param.name.value, Universe::Type)), + |expander| { + let params = lower_params(expander, params); + + let (returns, body) = expander.bind_many( + params + .iter() + .map(|param| (param.name.value, Universe::Value)), + |expander| { + expander.with_universe(Universe::Type, |expander| { + expander.visit(&mut r#return.value); + }); + + let returns = mem::replace(&mut r#return.value, Expr::dummy()); + let returns = lower_expr_to_type(expander, returns); + + expander + .with_universe(Universe::Value, |expander| expander.visit(&mut body.value)); + + let body = mem::replace(&mut body.value, Expr::dummy()); + + (returns, body) + }, + ); + + (params, returns, body) + }, + ); + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Closure(ClosureExpr { + id: NodeId::PLACEHOLDER, + span, + signature: Box::new_in( + ClosureSignature { + id: NodeId::PLACEHOLDER, + // TODO(BE-76): We can narrow this significantly if we have the possibility of + // span tree creation inside the AST + span, + generics, + inputs: params, + output: returns, + }, + expander.heap, + ), + body: Box::new_in(body, expander.heap), + }), + } +} + +/// Lowers a `fn` call into a [`ClosureExpr`]. +/// +/// Form: `(fn generics params return-type body)` where: +/// - `generics` is a tuple `(T, U)` or struct `(T: bound, U: _)` of type parameters +/// - `params` is a struct `(x: int, y: string)` of named parameters +/// - `return-type` is a type expression +/// - `body` is the function body +/// +/// Generic names are bound in the type universe. Parameter names are +/// bound in the value universe. Both are in scope for the return type +/// and body. +/// +/// [`ClosureExpr`]: crate::node::expr::ClosureExpr +pub(super) fn lower_fn<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_fn(labeled_arguments)); + } + + if let [generics, params, r#return, body] = &mut **arguments { + lower_fn_impl(*span, expander, generics, params, r#return, body) + } else { + expander + .diagnostics + .push(error::invalid_fn_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/if.rs b/libs/@local/hashql/ast/src/lower/expander/if.rs new file mode 100644 index 00000000000..8c91ee77255 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/if.rs @@ -0,0 +1,85 @@ +use core::mem; + +use hashql_core::{heap::BumpAllocator, span::SpanId}; + +use super::Expander; +use crate::{ + lower::expander::error, + node::{ + expr::{CallExpr, Expr, ExprKind, IfExpr, call::Argument}, + id::NodeId, + }, +}; + +fn lower_if_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + test: &mut Argument<'heap>, + then: &mut Argument<'heap>, + r#else: Option<&mut Argument<'heap>>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let mut test = mem::replace(&mut test.value, Expr::dummy()); + let mut then = mem::replace(&mut then.value, Expr::dummy()); + let mut r#else = r#else.map(|argument| mem::replace(&mut argument.value, Expr::dummy())); + + expander.visit(&mut test); + expander.visit(&mut then); + if let Some(r#else) = r#else.as_mut() { + expander.visit(r#else); + } + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::If(IfExpr { + id: NodeId::PLACEHOLDER, + span, + test: Box::new_in(test, expander.heap), + then: Box::new_in(then, expander.heap), + r#else: r#else.map(|r#else| Box::new_in(r#else, expander.heap)), + }), + } +} + +/// Lowers an `if` call into an [`IfExpr`]. +/// +/// Accepts two forms: +/// - `(if condition then)` without an else branch +/// - `(if condition then else)` with an else branch +/// +/// [`IfExpr`]: crate::node::expr::IfExpr +pub(super) fn lower_if<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_if(labeled_arguments)); + } + + match &mut **arguments { + [test, then] => lower_if_impl(*span, expander, test, then, None), + [test, then, r#else] => lower_if_impl(*span, expander, test, then, Some(r#else)), + _ => { + expander + .diagnostics + .push(error::invalid_if_argument_count(*span, arguments)); + + Expr::dummy() + } + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/index.rs b/libs/@local/hashql/ast/src/lower/expander/index.rs new file mode 100644 index 00000000000..d99f829f6e5 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/index.rs @@ -0,0 +1,76 @@ +use core::mem; + +use hashql_core::{heap::BumpAllocator, span::SpanId}; + +use super::Expander; +use crate::{ + lower::expander::error, + node::{ + expr::{CallExpr, Expr, ExprKind, IndexExpr, call::Argument}, + id::NodeId, + }, +}; + +fn lower_index_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + value: &mut Argument<'heap>, + index: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let mut value = mem::replace(&mut value.value, Expr::dummy()); + let mut index = mem::replace(&mut index.value, Expr::dummy()); + + expander.visit(&mut value); + expander.visit(&mut index); + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Index(IndexExpr { + id: NodeId::PLACEHOLDER, + span, + value: Box::new_in(value, expander.heap), + index: Box::new_in(index, expander.heap), + }), + } +} + +/// Lowers an `[]` call into an [`IndexExpr`]. +/// +/// Form: `([] collection index)`. Both arguments are resolved in the +/// current universe. +/// +/// [`IndexExpr`]: crate::node::expr::IndexExpr +pub(super) fn lower_index<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_index(labeled_arguments)); + } + + if let [value, index] = &mut **arguments { + lower_index_impl(*span, expander, value, index) + } else { + expander + .diagnostics + .push(error::invalid_index_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/input.rs b/libs/@local/hashql/ast/src/lower/expander/input.rs new file mode 100644 index 00000000000..9a2992610b6 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/input.rs @@ -0,0 +1,119 @@ +use core::mem; + +use hashql_core::{ + heap::BumpAllocator, + module::Universe, + span::SpanId, + symbol::{Ident, sym}, +}; + +use super::Expander; +use crate::{ + lower::expander::{error, r#type::lower_expr_to_type}, + node::{ + expr::{CallExpr, Expr, ExprKind, InputExpr, call::Argument}, + id::NodeId, + }, +}; + +fn argument_to_ident<'heap>(argument: &Argument<'heap>) -> Option> { + if let ExprKind::Path(path) = &argument.value.kind + && let Some(&ident) = path.as_ident() + { + Some(ident) + } else { + None + } +} + +fn lower_input_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + name: &Argument<'heap>, + r#type: &mut Argument<'heap>, + default: Option<&mut Argument<'heap>>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let name = if let Some(name) = argument_to_ident(name) { + name + } else { + expander + .diagnostics + .push(error::invalid_input_binding_name(name)); + + Ident::synthetic(sym::dummy) + }; + + let mut type_expr = mem::replace(&mut r#type.value, Expr::dummy()); + expander.with_universe(Universe::Type, |expander| { + expander.visit(&mut type_expr); + }); + let r#type = lower_expr_to_type(expander, type_expr); + + let default = default.map(|default| { + let mut value = mem::replace(&mut default.value, Expr::dummy()); + expander.visit(&mut value); + Box::new_in(value, expander.heap) + }); + + if name.value == sym::dummy { + return Expr::dummy(); + } + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Input(InputExpr { + id: NodeId::PLACEHOLDER, + span, + name, + r#type: Box::new_in(r#type, expander.heap), + default, + }), + } +} + +/// Lowers an `input` call into an [`InputExpr`]. +/// +/// Accepts two forms: +/// - `(input name type)` for a required input +/// - `(input name type default)` for an input with a default value +/// +/// The type is resolved in the type universe. The default (if present) +/// is resolved in the value universe. +/// +/// [`InputExpr`]: crate::node::expr::InputExpr +pub(super) fn lower_input<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_input(labeled_arguments)); + } + + match &mut **arguments { + [name, r#type] => lower_input_impl(*span, expander, name, r#type, None), + [name, r#type, default] => lower_input_impl(*span, expander, name, r#type, Some(default)), + _ => { + expander + .diagnostics + .push(error::invalid_input_argument_count(*span, arguments)); + + Expr::dummy() + } + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/let.rs b/libs/@local/hashql/ast/src/lower/expander/let.rs new file mode 100644 index 00000000000..69485d05c77 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/let.rs @@ -0,0 +1,164 @@ +use core::mem; + +use hashql_core::{ + heap::BumpAllocator, + module::item::Item, + span::SpanId, + symbol::{Ident, sym}, +}; + +use super::{BindingKind, CurrentItem, Expander, r#type::lower_expr_to_type}; +use crate::{ + lower::expander::error, + node::{ + expr::{CallExpr, Expr, ExprKind, LetExpr, call::Argument}, + id::NodeId, + }, +}; + +pub(super) fn expr_to_ident<'heap>(expr: &Expr<'heap>) -> Option> { + if let ExprKind::Path(path) = &expr.kind + && let Some(&ident) = path.as_ident() + { + Some(ident) + } else { + None + } +} + +pub(super) fn argument_to_ident<'heap>(argument: &Argument<'heap>) -> Option> { + expr_to_ident(&argument.value) +} + +fn lower_let_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + name: &Argument<'heap>, + value: &mut Argument<'heap>, + r#type: Option<&mut Argument<'heap>>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let name = if let Some(name) = argument_to_ident(name) { + name + } else { + expander + .diagnostics + .push(error::invalid_let_binding_name(name)); + + Ident::synthetic(sym::dummy) + }; + + let item = expander.visit(&mut value.value); + + let kind = item.filter(|item| !item.has_arguments).map_or( + BindingKind::Local(hashql_core::module::Universe::Value), + |item| BindingKind::Remote(item.item), + ); + + expander.bind(name.value, kind, |expander| { + expander.visit(&mut body.value); + }); + + if let Some(CurrentItem { + item: + current_module @ Item { + kind: hashql_core::module::item::ItemKind::Intrinsic(_), + .. + }, + // We cannot replace an alias with arguments, because we'd lose the arguments + has_arguments: false, + }) = item + && current_module.module == expander.special_form_module + { + if let Some(r#type) = r#type { + expander.diagnostics.push(error::intrinsic_type_annotation( + r#type.value.span, + value.value.span, + name.value, + )); + } + + // We do not replace every binding outright, only intrinsic ones for one specific reason: + // intrinsic types cannot be annotated, otherwise the binding survives, as the typechk on + // the binding could fail (or the typechk is used to narrow the type). + return mem::replace(&mut body.value, Expr::dummy()); + } + + let r#type = if let Some(r#type) = r#type { + let mut value = mem::replace(&mut r#type.value, Expr::dummy()); + + expander.with_universe(hashql_core::module::Universe::Type, |expander| { + expander.visit(&mut value); + }); + + Some(lower_expr_to_type(expander, value)) + } else { + None + }; + + if name.value == sym::dummy { + return Expr::dummy(); + } + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Let(LetExpr { + id: NodeId::PLACEHOLDER, + span, + name, + value: Box::new_in(mem::replace(&mut value.value, Expr::dummy()), expander.heap), + r#type: r#type.map(|r#type| Box::new_in(r#type, expander.heap)), + body: Box::new_in(mem::replace(&mut body.value, Expr::dummy()), expander.heap), + }), + } +} + +/// Lowers a `let` call into a [`LetExpr`]. +/// +/// Accepts two forms: +/// - `(let name value body)` with no type annotation +/// - `(let name type value body)` with an explicit type +/// +/// The `name` is bound in scope for `body` only. +/// +/// [`LetExpr`]: crate::node::expr::LetExpr +pub(super) fn lower_let<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + // We continue, to try to recover, if that means that the user has a labeled argument + // instead of a positional one we error twice, but that is deemed acceptable. + expander + .diagnostics + .push(error::labeled_arguments_in_let(labeled_arguments)); + } + + match &mut **arguments { + [name, value, body] => lower_let_impl(*span, expander, name, value, None, body), + [name, r#type, value, body] => { + lower_let_impl(*span, expander, name, value, Some(r#type), body) + } + _ => { + expander + .diagnostics + .push(error::invalid_let_argument_count(*span, arguments)); + + Expr::dummy() + } + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/mod.rs b/libs/@local/hashql/ast/src/lower/expander/mod.rs new file mode 100644 index 00000000000..6d8db089cf7 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/mod.rs @@ -0,0 +1,487 @@ +//! Combined name resolution and special form expansion pass. +//! +//! The expander walks the AST top-down, resolving every path against a +//! [`ModuleNamespace`] and rewriting it to its absolute form. When a call +//! expression targets a special form (like `let`, `fn`, or `use`), the +//! expander lowers it into the corresponding typed AST node in the same +//! traversal. + +mod access; +mod r#as; +pub mod error; +mod r#fn; +mod r#if; +mod index; +mod input; +mod r#let; +mod newtype; +mod r#type; +mod r#use; + +use core::mem; + +use hashql_core::{ + heap::{self, BumpAllocator}, + module::{ + self, Reference, Universe, + item::{IntrinsicItem, IntrinsicTypeItem, Item, ItemKind}, + namespace::{ModuleNamespace, ResolutionMode, ResolveOptions}, + }, + symbol::{Ident, Symbol, sym}, +}; + +use self::{ + access::lower_access, r#as::lower_as, error::ExpanderDiagnosticIssues, r#fn::lower_fn, + r#if::lower_if, index::lower_index, input::lower_input, r#let::lower_let, + newtype::lower_newtype, r#type::lower_type, r#use::lower_use, +}; +use crate::{ + node::{self, expr::CallExpr, id::NodeId}, + visit::{self, Visitor}, +}; + +/// Whether a binding introduced during expansion is a local (opaque) or +/// an alias for a resolved registry item. +#[derive(Debug)] +enum BindingKind<'heap> { + /// A binding with no identity beyond its name and universe + /// (e.g., a `let` binding or function parameter). + Local(Universe), + /// An alias for a known registry item, preserving its identity through + /// rebinding (e.g., a `use` import). + Remote(Item<'heap>), +} + +impl<'heap> From> for BindingKind<'heap> { + fn from(value: Item<'heap>) -> Self { + Self::Remote(value) + } +} + +impl From for BindingKind<'_> { + fn from(value: Universe) -> Self { + Self::Local(value) + } +} + +/// Handle for registering bindings without exposing the full namespace. +/// +/// Passed into the `register` closure of [`Expander::bind_many_with`] so +/// that the closure can declare bindings while the caller retains a +/// separate mutable reference to its own data. +struct Binder<'ns, 'env, 'heap> { + namespace: &'ns mut ModuleNamespace<'env, 'heap>, +} + +impl<'heap> Binder<'_, '_, 'heap> { + fn bind(&mut self, symbol: Symbol<'heap>, kind: impl Into>) { + match kind.into() { + BindingKind::Local(universe) => self.namespace.local(symbol, universe), + BindingKind::Remote(item) => self.namespace.alias(symbol, item), + } + } +} + +#[derive(Debug, Copy, Clone)] +struct CurrentItem<'heap> { + item: module::item::Item<'heap>, + has_arguments: bool, +} + +/// Combined name resolution and special form expansion visitor. +/// +/// Resolves every path in the AST against the module namespace, rewrites +/// paths to their absolute form, and lowers special form calls (e.g., `let`, +/// `fn`, `use`) into typed AST nodes. Errors that prevent resolution produce +/// dummy expression placeholders so that later diagnostics can be +/// suppressed for cascading failures. +pub struct Expander<'env, 'heap, S> { + heap: &'heap heap::Heap, + scratch: S, + + namespace: ModuleNamespace<'env, 'heap>, + current_universe: Universe, + diagnostics: ExpanderDiagnosticIssues, + + current_item: Option>, + trampoline: Option>, + special_form_module: module::ModuleId, +} + +impl<'env, 'heap, S> Expander<'env, 'heap, S> { + /// Creates a new [`Expander`] with the given namespace and scratch space. + /// + /// # Panics + /// + /// Panics if the standard library does not contain the kernel module. + pub fn new(mut namespace: ModuleNamespace<'env, 'heap>, scratch: S) -> Self { + // First we need to find the special form module, this is used during resolution to forbid + // the use of generics attached to them. + let kernel_module = namespace + .registry() + .find_by_name(sym::kernel) + .unwrap_or_else(|| { + namespace.import_prelude(); + + namespace + .registry() + .find_by_name(sym::kernel) + .expect("prelude should have been loaded and include the kernel") + }); + + let Some(&Item { + kind: ItemKind::Module(special_form_module), + .. + }) = kernel_module + .items + .iter() + .find(|item| item.name == sym::special_form) + else { + unreachable!("kernel module should always contain the special form module"); + }; + + Self { + heap: namespace.registry().heap, + namespace, + scratch, + current_universe: Universe::Value, + diagnostics: ExpanderDiagnosticIssues::new(), + current_item: None, + trampoline: None, + special_form_module, + } + } + + pub fn take_diagnostics(&mut self) -> ExpanderDiagnosticIssues { + mem::take(&mut self.diagnostics) + } + + /// Walks `expr`, applying resolution and expansion, and returns the + /// resolved [`Item`] if the expression was a path that resolved to one. + /// + /// Returns `None` when the expression resolved to a local binding, + /// was not a path, or resolution failed (in which case the expression + /// is replaced with a dummy expression). + fn visit(&mut self, expr: &mut node::expr::Expr<'heap>) -> Option> + where + S: BumpAllocator, + { + let prev_current_item = self.current_item.take(); + visit::walk_expr(self, expr); + + if let Some(trampoline) = self.trampoline.take() { + *expr = trampoline; + } + + let current_item = self.current_item.take(); + self.current_item = prev_current_item; + + current_item + } + + /// Runs `closure` with the resolution universe temporarily set to + /// `universe`, restoring the previous universe afterwards. + fn with_universe(&mut self, universe: Universe, closure: impl FnOnce(&mut Self) -> T) -> T { + let prev_universe = self.current_universe; + self.current_universe = universe; + let result = closure(self); + self.current_universe = prev_universe; + result + } + + /// Introduces a single binding for the duration of `closure`, then + /// rolls back the namespace. + fn bind( + &mut self, + variable: Symbol<'heap>, + kind: impl Into>, + closure: impl FnOnce(&mut Self) -> T, + ) -> T { + self.bind_many([(variable, kind)], closure) + } + + /// Introduces bindings derived from `value` and then runs `closure` + /// with both `&mut Self` and `&mut U` available. + /// + /// `register` inspects `value` (shared) and pushes bindings through + /// the [`Binder`]. Once it returns, `closure` receives full mutable + /// access to both the expander and `value`. The namespace is rolled + /// back after `closure` completes. + /// + /// This two-closure design exists because Rust closures cannot return + /// iterators that borrow from their arguments. The push-style + /// `register` avoids that limitation without dynamic dispatch. + fn bind_many_with( + &mut self, + mut value: U, + register: impl FnOnce(&U, &mut Binder<'_, '_, 'heap>), + closure: impl FnOnce(&mut Self, &mut U) -> T, + ) -> (U, T) { + let snapshot = self.namespace.snapshot(); + + let mut binder = Binder { + namespace: &mut self.namespace, + }; + register(&value, &mut binder); + + let result = closure(self, &mut value); + + self.namespace.rollback_to(snapshot); + + (value, result) + } + + /// Introduces all `variables` as bindings for the duration of `closure`, + /// then rolls back the namespace. + fn bind_many( + &mut self, + variables: impl IntoIterator, K)>, + closure: impl FnOnce(&mut Self) -> T, + ) -> T + where + K: Into>, + { + // The inline is here, because this is a hot path, this is just desugaring of the + // underlying iterator, make it explicit so that it indeed inlines the call + let ((), result) = self.bind_many_with( + (), + #[inline] + |(), binder| { + for (variable, kind) in variables { + binder.bind(variable, kind); + } + }, + #[inline] + |this, ()| closure(this), + ); + + result + } +} + +impl<'heap, S> Visitor<'heap> for Expander<'_, 'heap, S> +where + S: BumpAllocator, +{ + #[expect( + clippy::too_many_lines, + reason = "actual algorithm is straightforward, error reporting is what balloons it" + )] + fn visit_path(&mut self, path: &mut node::path::Path<'heap>) { + visit::walk_path(self, path); + self.current_item = None; + + let [modules @ .., ident] = &*path.segments else { + self.diagnostics.push(error::empty_path(path.span)); + self.trampoline = Some(node::expr::Expr::dummy()); + + return; + }; + + // We don't support generics except for the *last* segment + if let Some(diagnostic) = error::generic_arguments_in_module(modules) { + self.diagnostics.push(diagnostic); + self.trampoline = Some(node::expr::Expr::dummy()); + + return; + } + + let reference = self.namespace.resolve( + path.segments.iter().map(|segment| segment.name.value), + ResolveOptions { + universe: self.current_universe, + mode: if path.rooted { + ResolutionMode::Absolute + } else { + ResolutionMode::Relative + }, + }, + ); + + let reference = match reference { + Ok(reference) => reference, + Err(error) => { + self.diagnostics.push(error::from_resolution_error( + path, + &self.namespace, + self.current_universe, + error, + )); + self.trampoline = Some(node::expr::Expr::dummy()); + + return; + } + }; + + let item = match reference { + Reference::Binding(_) => { + debug_assert_eq!( + path.segments.len(), + 1, + "a binding should always only have a single segment" + ); + + return; + } + Reference::Item(item) => item, + }; + + self.current_item = Some(CurrentItem { + item, + has_arguments: !ident.arguments.is_empty(), + }); + + // Make sure that the intrinsic special forms do not have arguments attached to them. + match item.kind { + ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { name })) + if let Some(const_name) = name.as_constant() + && matches!( + const_name, + sym::path::Union::CONST | sym::path::Intersection::CONST + ) + && !ident.arguments.is_empty() => + { + self.diagnostics + .push(error::intrinsic_generic_arguments(ident)); + self.trampoline = Some(node::expr::Expr::dummy()); + + return; + } + ItemKind::Intrinsic(IntrinsicItem::Value(_)) + if !ident.arguments.is_empty() && item.module == self.special_form_module => + { + self.diagnostics + .push(error::intrinsic_generic_arguments(ident)); + self.trampoline = Some(node::expr::Expr::dummy()); + + return; + } + ItemKind::Module(_) + | ItemKind::Type(_) + | ItemKind::Constructor(_) + | ItemKind::Intrinsic(_) => {} + } + + let absolute_path = item.absolute_path_rev(self.namespace.registry()); + + if absolute_path.len() >= path.segments.len() { + // The canonical path is longer (or equal): pad with placeholder segments, + // rotate the originals into position, then fill in the canonical names. + let padding = absolute_path.len() - path.segments.len(); + let padding_span = path.segments[0].span; + + path.segments.extend(core::iter::repeat_n( + node::path::PathSegment { + id: NodeId::PLACEHOLDER, + span: padding_span, + name: Ident { + span: padding_span, + value: sym::dummy, + kind: hashql_core::symbol::IdentKind::Lexical, + }, + arguments: heap::Vec::new_in(self.heap), + }, + padding, + )); + path.segments.rotate_right(padding); + } else { + // The canonical path is shorter (re-export through a longer path): + // truncate the extra leading segments. + let excess = path.segments.len() - absolute_path.len(); + path.segments.rotate_left(excess); + path.segments.truncate(absolute_path.len()); + } + + for (segment, name) in path.segments.iter_mut().rev().zip(absolute_path) { + segment.name.value = name; + } + + path.rooted = true; + } + + fn visit_call_expr(&mut self, expr: &mut node::expr::CallExpr<'heap>) { + let item = self.visit(&mut expr.function); + + if let Some(CurrentItem { + item: + Item { + kind: module::item::ItemKind::Intrinsic(IntrinsicItem::Value(value)), + .. + }, + has_arguments: _, // We ignore it so that we can continue to lower here + }) = item + && let Some(constant) = value.name.as_constant() + { + match constant { + sym::path::r#if::CONST => { + self.trampoline = Some(lower_if(self, expr)); + return; + } + sym::path::r#as::CONST => { + self.trampoline = Some(lower_as(self, expr)); + return; + } + sym::path::r#let::CONST => { + self.trampoline = Some(lower_let(self, expr)); + return; + } + sym::path::r#type::CONST => { + self.trampoline = Some(lower_type(self, expr)); + return; + } + sym::path::newtype::CONST => { + self.trampoline = Some(lower_newtype(self, expr)); + return; + } + sym::path::r#use::CONST => { + self.trampoline = Some(lower_use(self, expr)); + return; + } + sym::path::r#fn::CONST => { + self.trampoline = Some(lower_fn(self, expr)); + return; + } + sym::path::input::CONST => { + self.trampoline = Some(lower_input(self, expr)); + return; + } + sym::path::index::CONST => { + self.trampoline = Some(lower_index(self, expr)); + return; + } + sym::path::access::CONST => { + self.trampoline = Some(lower_access(self, expr)); + return; + } + _ => {} + } + } + + // We haven't encountered a special-form, meaning that we can treat this as a regular call + // expression + let CallExpr { + id: _, + span: _, + function: _, // we already visited the function + arguments, + labeled_arguments, + } = expr; + + for argument in arguments { + self.visit_argument(argument); + } + for labeled_argument in labeled_arguments { + self.visit_labeled_argument(labeled_argument); + } + } + + fn visit_expr(&mut self, expr: &mut node::expr::Expr<'heap>) { + self.visit(expr); + } + + fn visit_type(&mut self, r#type: &mut node::r#type::Type<'heap>) { + self.with_universe(Universe::Type, |this| { + visit::walk_type(this, &mut *r#type); + }); + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/newtype.rs b/libs/@local/hashql/ast/src/lower/expander/newtype.rs new file mode 100644 index 00000000000..15dbf8d0b61 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/newtype.rs @@ -0,0 +1,124 @@ +use core::mem; + +use hashql_core::{ + heap::{self, BumpAllocator}, + module::Universe, + span::SpanId, + symbol::{Ident, sym}, +}; + +use super::Expander; +use crate::{ + lower::expander::{ + error, + r#type::{argument_to_generic_ident, lower_expr_to_type}, + }, + node::{ + expr::{CallExpr, Expr, ExprKind, NewTypeExpr, call::Argument}, + id::NodeId, + }, +}; + +fn lower_newtype_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + name: &mut Argument<'heap>, + value: &mut Argument<'heap>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let (name, constraints) = if let Some(result) = argument_to_generic_ident(expander, name) { + result + } else { + expander + .diagnostics + .push(error::invalid_newtype_binding_name(name)); + + ( + Ident::synthetic(sym::dummy), + heap::Vec::new_in(expander.heap), + ) + }; + + let mut value = mem::replace(&mut value.value, Expr::dummy()); + let mut body = mem::replace(&mut body.value, Expr::dummy()); + + let (_, expr) = expander.bind_many_with( + constraints, + |constraints, binder| { + binder.bind(name.value, Universe::Type); + binder.bind(name.value, Universe::Value); + for constraint in constraints { + binder.bind(constraint.name.value, Universe::Type); + } + }, + |expander, constraints| { + expander.with_universe(Universe::Type, |expander| { + expander.visit(&mut value); + }); + + expander.visit(&mut body); + let value = lower_expr_to_type(expander, value); + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::NewType(NewTypeExpr { + id: NodeId::PLACEHOLDER, + span, + name, + constraints: mem::replace(constraints, Vec::new_in(expander.heap)), + value: Box::new_in(value, expander.heap), + body: Box::new_in(body, expander.heap), + }), + } + }, + ); + + if name.value == sym::dummy { + return Expr::dummy(); + } + + expr +} + +/// Lowers a `newtype` call into a [`NewTypeExpr`]. +/// +/// Form: `(newtype Name type-expr body)`. Like `type`, but introduces a +/// distinct nominal type rather than an alias. The name is bound in both +/// the type and value universes for `body` (the value binding acts as a +/// constructor). +/// +/// [`NewTypeExpr`]: crate::node::expr::NewTypeExpr +pub(super) fn lower_newtype<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_newtype(labeled_arguments)); + } + + if let [name, value, body] = &mut **arguments { + lower_newtype_impl(*span, expander, name, value, body) + } else { + expander + .diagnostics + .push(error::invalid_newtype_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/type.rs b/libs/@local/hashql/ast/src/lower/expander/type.rs new file mode 100644 index 00000000000..c55254cca07 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/type.rs @@ -0,0 +1,486 @@ +use core::{iter, mem}; + +use hashql_core::{ + collections::fast_hash_map_with_capacity_in, + heap::{self, BumpAllocator, Heap}, + module::{ + Universe, + item::{IntrinsicItem, IntrinsicTypeItem, Item}, + }, + span::SpanId, + symbol::{Ident, sym}, +}; + +use super::{CurrentItem, Expander, error::ExpanderDiagnosticIssues}; +use crate::{ + lower::expander::error, + node::{ + expr::{CallExpr, Expr, ExprKind, TypeExpr, call::Argument}, + generic::{self, GenericConstraint}, + id::NodeId, + path::PathSegmentArgument, + r#type::{IntersectionType, StructType, TupleType, Type, TypeKind, UnionType}, + }, + visit::Visitor as _, +}; + +fn lower_call_to_type<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + mut call: CallExpr<'heap>, +) -> Type<'heap> +where + S: BumpAllocator, +{ + if !call.labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_type_call( + &call.labeled_arguments, + )); + return Type::dummy(); + } + + let Some(CurrentItem { + item: + Item { + module: _, + name: _, + kind: + hashql_core::module::item::ItemKind::Intrinsic(IntrinsicItem::Type(type_intrinsic)), + }, + has_arguments: _, // We don't care here, intrinsics never have arguments + }) = expander.with_universe(hashql_core::module::Universe::Type, |expander| { + expander.visit(&mut call.function) + }) + else { + // Only emit a diagnostic if the function didn't become Dummy from an earlier + // resolution error. Dummy means a resolution diagnostic was already emitted. + if !matches!(call.function.kind, ExprKind::Dummy) { + expander + .diagnostics + .push(error::invalid_type_constructor_call( + call.function.span, + call.span, + )); + } + + return Type::dummy(); + }; + + let mut types = Vec::with_capacity_in(call.arguments.len(), expander.heap); + for argument in call.arguments { + types.push(lower_expr_to_type(expander, argument.value)); + } + + let kind = match type_intrinsic.name.as_constant() { + Some(sym::path::Union::CONST) => TypeKind::Union(UnionType { + id: NodeId::PLACEHOLDER, + span: call.span, + types, + }), + Some(sym::path::Intersection::CONST) => TypeKind::Intersection(IntersectionType { + id: NodeId::PLACEHOLDER, + span: call.span, + types, + }), + _ => { + expander.diagnostics.push(error::type_is_not_callable( + type_intrinsic.name, + call.function.span, + call.span, + )); + return Type::dummy(); + } + }; + + Type { + id: NodeId::PLACEHOLDER, + span: call.span, + kind, + } +} + +/// Converts a value-position [`Expr`] into a [`Type`]. +/// +/// Paths, tuples, structs, `_`, and type constructor calls (`|`, `&`) are +/// valid. Everything else produces a diagnostic and returns [`Type::dummy`]. +/// [`ExprKind::Dummy`] is suppressed (a resolution error was already reported). +pub(super) fn lower_expr_to_type<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + expr: Expr<'heap>, +) -> Type<'heap> +where + S: BumpAllocator, +{ + match expr.kind { + ExprKind::Call(call) => lower_call_to_type(expander, call), + ExprKind::Tuple(tuple) => { + if let Some(annotation) = &tuple.r#type { + // We continue, because it's not fatal, we just ignore it, compilation will + // terminate before ever reaching the HIR. + expander + .diagnostics + .push(error::type_annotation_in_type_position( + annotation.span, + expr.span, + error::AggregateKind::Tuple, + )); + } + + let mut elements = Vec::with_capacity_in(tuple.elements.len(), expander.heap); + + for element in tuple.elements { + elements.push(crate::node::r#type::TupleField { + id: NodeId::PLACEHOLDER, + span: element.span, + r#type: lower_expr_to_type(expander, element.value), + }); + } + + Type { + id: NodeId::PLACEHOLDER, + span: expr.span, + kind: TypeKind::Tuple(TupleType { + id: NodeId::PLACEHOLDER, + span: expr.span, + fields: elements, + }), + } + } + ExprKind::Struct(r#struct) => { + if let Some(annotation) = &r#struct.r#type { + // We continue, because it's not fatal, we just ignore it, compilation will + // terminate before ever reaching the HIR. + expander + .diagnostics + .push(error::type_annotation_in_type_position( + annotation.span, + expr.span, + error::AggregateKind::Struct, + )); + } + + let mut fields = Vec::with_capacity_in(r#struct.entries.len(), expander.heap); + + for entry in r#struct.entries { + fields.push(crate::node::r#type::StructField { + id: NodeId::PLACEHOLDER, + span: entry.span, + name: entry.key, + r#type: lower_expr_to_type(expander, entry.value), + }); + } + + Type { + id: NodeId::PLACEHOLDER, + span: expr.span, + kind: TypeKind::Struct(StructType { + id: NodeId::PLACEHOLDER, + span: expr.span, + fields, + }), + } + } + ExprKind::Path(path) => Type { + id: NodeId::PLACEHOLDER, + span: expr.span, + kind: TypeKind::Path(path), + }, + ExprKind::Underscore => Type { + id: NodeId::PLACEHOLDER, + span: expr.span, + kind: TypeKind::Infer, + }, + ExprKind::Dict(_) + | ExprKind::List(_) + | ExprKind::Literal(_) + | ExprKind::Let(_) + | ExprKind::Type(_) + | ExprKind::NewType(_) + | ExprKind::Input(_) + | ExprKind::Closure(_) + | ExprKind::If(_) + | ExprKind::Field(_) + | ExprKind::Index(_) + | ExprKind::As(_) + | ExprKind::Dummy => { + if let Some(diagnostic) = + error::invalid_expression_in_type_position(expr.span, &expr.kind) + { + expander.diagnostics.push(diagnostic); + } + + Type { + id: NodeId::PLACEHOLDER, + span: expr.span, + kind: TypeKind::Dummy, + } + } + } +} + +fn path_arguments_to_constraints_unvalidated<'heap, S>( + heap: &'heap Heap, + scratch: S, + diagnostics: &mut ExpanderDiagnosticIssues, + + arguments: &mut [PathSegmentArgument<'heap>], +) -> heap::Vec<'heap, GenericConstraint<'heap>> +where + S: BumpAllocator, +{ + let mut constraints = heap::Vec::with_capacity_in(arguments.len(), heap); + let mut seen = fast_hash_map_with_capacity_in(arguments.len(), scratch); + + for argument in arguments { + match argument { + PathSegmentArgument::Argument(generic::GenericArgument { + id, + span, + r#type: + Type { + id: _, + span: _, + kind: TypeKind::Path(path), + }, + }) if let Some(&ident) = path.as_ident() => { + // In this case it's simply interpreted as a generic constraint with no bounds. + if let Err(error) = seen.try_insert(ident.value, ident.span) { + diagnostics.push(error::duplicate_generic_constraint( + ident.span, + ident.value, + *error.entry.get(), + )); + + continue; + } + + constraints.push(GenericConstraint { + id: *id, + span: *span, + name: ident, + bound: None, + }); + } + PathSegmentArgument::Argument(generic_argument) => { + let is_path = matches!(generic_argument.r#type.kind, TypeKind::Path(_)); + let span = if is_path { + generic_argument.r#type.span + } else { + generic_argument.span + }; + + diagnostics.push(error::invalid_generic_argument(span, is_path)); + } + PathSegmentArgument::Constraint(generic_constraint) => { + if let Err(error) = + seen.try_insert(generic_constraint.name.value, generic_constraint.name.span) + { + diagnostics.push(error::duplicate_generic_constraint( + generic_constraint.name.span, + generic_constraint.name.value, + *error.entry.get(), + )); + + continue; + } + + constraints.push(GenericConstraint { + id: NodeId::PLACEHOLDER, + span: generic_constraint.span, + name: generic_constraint.name, + bound: generic_constraint.bound.take(), + }); + } + } + } + + constraints +} + +pub(super) fn argument_to_generic_ident<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + + argument: &mut Argument<'heap>, +) -> Option<(Ident<'heap>, heap::Vec<'heap, GenericConstraint<'heap>>)> +where + S: BumpAllocator, +{ + if let ExprKind::Path(path) = &mut argument.value.kind + && let Some((name, arguments)) = path.as_generic_ident_mut() + { + // Generic constraints _may_ be recursive. To allow for that we must put the name of the + // ident into view before we do anything + let constraints = expander.scratch.scoped(|scratch| { + path_arguments_to_constraints_unvalidated( + expander.heap, + scratch, + &mut expander.diagnostics, + arguments, + ) + }); + + // constraints are not yet validated (which is what we're doing now) + let (constraints, ()) = expander.bind_many_with( + constraints, + |constraints, binder| { + iter::chain( + iter::once((name.value, Universe::Type)), + constraints + .iter() + .map(|constraint| (constraint.name.value, Universe::Type)), + ) + .for_each(|(symbol, universe)| { + binder.bind(symbol, universe); + }); + }, + |expander, constraints| { + // The constraints _have not yet_ been validated, this is on purpose, because we + // must first convert them, now that they are converted, we can + // validate them. + + for constraint in constraints { + if let Some(bound) = constraint.bound.as_mut() { + expander.visit_type(bound); + } + } + }, + ); + + Some((name, constraints)) + } else { + None + } +} + +fn lower_type_impl<'heap, S>( + span: SpanId, + expander: &mut Expander<'_, 'heap, S>, + + name: &mut Argument<'heap>, + value: &mut Argument<'heap>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let (name, constraints) = if let Some(result) = argument_to_generic_ident(expander, name) { + result + } else { + expander + .diagnostics + .push(error::invalid_type_binding_name(name)); + + ( + Ident::synthetic(sym::dummy), + heap::Vec::new_in(expander.heap), + ) + }; + + let mut value = mem::replace(&mut value.value, Expr::dummy()); + let mut body = mem::replace(&mut body.value, Expr::dummy()); + + let (_, expr) = expander.bind_many_with( + constraints, + |constraints, binder| { + binder.bind(name.value, Universe::Type); + for constraint in constraints { + binder.bind(constraint.name.value, Universe::Type); + } + }, + |expander, constraints| { + let item = expander.with_universe(hashql_core::module::Universe::Type, |expander| { + expander.visit(&mut value) + }); + + if let Some(CurrentItem { + item: + item @ Item { + kind: + hashql_core::module::item::ItemKind::Intrinsic(IntrinsicItem::Type( + IntrinsicTypeItem { + name: intrinsic_name, + }, + )), + .. + }, + // We cannot replace an alias with arguments, because we'd lose the arguments + has_arguments: false, + }) = item + && let Some(const_name) = intrinsic_name.as_constant() + && matches!( + const_name, + sym::path::Union::CONST | sym::path::Intersection::CONST + ) + { + // We rebound an intrinsic, instead of erroring out, we use the body + // directly, and "show" the new value: + expander.bind(name.value, item, |expander| expander.visit(&mut body)); + + return body; + } + + expander.visit(&mut body); + let value = lower_expr_to_type(expander, value); + + Expr { + id: NodeId::PLACEHOLDER, + span, + kind: ExprKind::Type(TypeExpr { + id: NodeId::PLACEHOLDER, + span, + name, + constraints: mem::replace(constraints, Vec::new_in(expander.heap)), + value: Box::new_in(value, expander.heap), + body: Box::new_in(body, expander.heap), + }), + } + }, + ); + + if name.value == sym::dummy { + return Expr::dummy(); + } + + expr +} + +/// Lowers a `type` call into a [`TypeExpr`]. +/// +/// Form: `(type Name type-expr body)`. The name may include generic +/// parameters like `Pair`. Constraints on generic parameters are +/// validated after all parameter names are in scope, so recursive +/// constraints like `T: Container` are supported. +/// +/// The name is bound in the type universe for `body`. +/// +/// [`TypeExpr`]: crate::node::expr::TypeExpr +pub(super) fn lower_type<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_type(labeled_arguments)); + } + + if let [name, value, body] = &mut **arguments { + lower_type_impl(*span, expander, name, value, body) + } else { + expander + .diagnostics + .push(error::invalid_type_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/expander/use.rs b/libs/@local/hashql/ast/src/lower/expander/use.rs new file mode 100644 index 00000000000..ff85964ce44 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/expander/use.rs @@ -0,0 +1,351 @@ +use core::{iter, mem}; + +use hashql_core::{ + collections::fast_hash_map_with_capacity_in, + heap::BumpAllocator, + module::namespace::{ImportOptions, ResolutionMode}, + symbol::sym, +}; + +use super::Expander; +use crate::{ + lower::expander::error, + node::{ + expr::{ + CallExpr, Expr, ExprKind, + call::Argument, + r#use::{self, UseBinding, UseKind}, + }, + id::NodeId, + path::Path, + }, +}; + +fn lower_imports_tuple<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + tuple: &mut crate::node::expr::TupleExpr<'heap>, +) -> UseKind<'heap> { + if let Some(annotation) = tuple.r#type.as_ref() { + expander + .diagnostics + .push(error::use_imports_type_annotation(annotation.span)); + } + + let mut bindings = Vec::with_capacity_in(tuple.elements.len(), expander.heap); + + for element in tuple.elements.drain(..) { + let ExprKind::Path(path) = element.value.kind else { + expander + .diagnostics + .push(error::invalid_use_import_binding(element.value.span)); + continue; + }; + + let Some(name) = path.into_ident() else { + expander + .diagnostics + .push(error::invalid_use_import_binding(element.value.span)); + continue; + }; + + bindings.push(UseBinding { + id: NodeId::PLACEHOLDER, + span: element.span, + name, + alias: None, + }); + } + + UseKind::Named(bindings) +} + +fn lower_imports_struct<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + r#struct: &mut crate::node::expr::StructExpr<'heap>, +) -> UseKind<'heap> { + if let Some(annotation) = r#struct.r#type.as_ref() { + expander + .diagnostics + .push(error::use_imports_type_annotation(annotation.span)); + } + + let mut bindings = Vec::with_capacity_in(r#struct.entries.len(), expander.heap); + + for entry in r#struct.entries.drain(..) { + let alias = match &entry.value.kind { + ExprKind::Underscore => None, + ExprKind::Path(path) if let Some(&ident) = path.as_ident() => Some(ident), + ExprKind::Call(_) + | ExprKind::Struct(_) + | ExprKind::Dict(_) + | ExprKind::Tuple(_) + | ExprKind::List(_) + | ExprKind::Literal(_) + | ExprKind::Path(_) + | ExprKind::Let(_) + | ExprKind::Type(_) + | ExprKind::NewType(_) + | ExprKind::Input(_) + | ExprKind::Closure(_) + | ExprKind::If(_) + | ExprKind::Field(_) + | ExprKind::Index(_) + | ExprKind::As(_) + | ExprKind::Dummy => { + expander + .diagnostics + .push(error::invalid_use_alias(entry.value.span)); + None + } + }; + + bindings.push(UseBinding { + id: NodeId::PLACEHOLDER, + span: entry.span, + name: entry.key, + alias, + }); + } + + UseKind::Named(bindings) +} + +fn lower_imports<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + + imports: &mut Argument<'heap>, +) -> Option> +where + S: BumpAllocator, +{ + let mut imports = match &mut imports.value.kind { + ExprKind::Path(path) + if let Some(&ident) = path.as_ident() + && ident.value.as_constant() == Some(sym::symbol::asterisk::CONST) => + { + Some(UseKind::Glob(r#use::Glob { + id: NodeId::PLACEHOLDER, + span: ident.span, + })) + } + ExprKind::Tuple(tuple) => Some(lower_imports_tuple(expander, tuple)), + ExprKind::Struct(r#struct) => Some(lower_imports_struct(expander, r#struct)), + ExprKind::Call(_) + | ExprKind::Dict(_) + | ExprKind::List(_) + | ExprKind::Literal(_) + | ExprKind::Path(_) + | ExprKind::Let(_) + | ExprKind::Type(_) + | ExprKind::NewType(_) + | ExprKind::Input(_) + | ExprKind::Closure(_) + | ExprKind::If(_) + | ExprKind::Field(_) + | ExprKind::Index(_) + | ExprKind::As(_) + | ExprKind::Underscore + | ExprKind::Dummy => { + expander + .diagnostics + .push(error::invalid_use_imports(imports.value.span)); + None + } + }; + + if let Some(UseKind::Named(bindings)) = &mut imports { + let diagnostics = &mut expander.diagnostics; + expander.scratch.scoped(|scratch| { + let mut seen = fast_hash_map_with_capacity_in(bindings.len(), scratch); + + bindings.retain(|binding| { + let effective_name = binding.alias.unwrap_or(binding.name); + + if let Err(occupied) = seen.try_insert(effective_name.value, binding.span) { + diagnostics.push(error::duplicate_use_binding( + binding.span, + effective_name.value, + *occupied.entry.get(), + )); + return false; + } + + true + }); + }); + } + + imports +} + +fn lower_body<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + path: &Path<'heap>, + imports: UseKind<'heap>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let mode = if path.rooted { + ResolutionMode::Absolute + } else { + ResolutionMode::Relative + }; + let query = path.segments.iter().map(|segment| segment.name.value); + + match imports { + UseKind::Named(use_bindings) => { + let mut errored = false; + + for UseBinding { + id: _, + span: _, + name, + alias, + } in use_bindings + { + let alias = alias.map_or(name.value, |alias| alias.value); + + let result = expander.namespace.import( + alias, + query.clone().chain(iter::once(name.value)), + ImportOptions { + glob: false, + mode, + suggestions: true, + }, + ); + + if let Err(error) = result { + expander + .diagnostics + .push(error::from_import_resolution_error( + path, + Some(name.symbol()), + error, + )); + + errored = true; + } + } + + let mut body = mem::replace(&mut body.value, Expr::dummy()); + expander.visit(&mut body); + + if errored { + return Expr::dummy(); + } + + body + } + UseKind::Glob(_) => { + let result = expander.namespace.import( + sym::symbol::asterisk, + query, + ImportOptions { + glob: true, + mode, + suggestions: true, + }, + ); + + let mut body = mem::replace(&mut body.value, Expr::dummy()); + expander.visit(&mut body); + + if let Err(error) = result { + expander + .diagnostics + .push(error::from_import_resolution_error(path, None, error)); + return Expr::dummy(); + } + + body + } + } +} + +fn lower_use_impl<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + + path: &mut Argument<'heap>, + imports: &mut Argument<'heap>, + body: &mut Argument<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + let path_expr = mem::replace(&mut path.value, Expr::dummy()); + let path = if let ExprKind::Path(path) = path_expr.kind { + if path.has_generic_arguments() { + expander + .diagnostics + .push(error::use_path_generic_arguments(path.span)); + // non-fatal: continue with the path + } + Some(path) + } else { + expander + .diagnostics + .push(error::invalid_use_path(path_expr.span)); + None + }; + + let imports = lower_imports(expander, imports); + + // If either path or imports are invalid, we still visit the body + // to collect diagnostics from nested expressions. + let Some((path, imports)) = path.zip(imports) else { + expander.visit(&mut body.value); + return Expr::dummy(); + }; + + let snapshot = expander.namespace.snapshot(); + let body = lower_body(expander, &path, imports, body); + expander.namespace.rollback_to(snapshot); + + body +} + +/// Lowers a `use` call, importing names into scope for the body. +/// +/// Form: `(use path imports body)` where: +/// - `path` is a module path like `core::math` +/// - `imports` is one of: +/// - `*` for a glob import +/// - `(sin, cos)` for named imports +/// - `(sin: my_sin, cos: _)` for aliased imports (`_` keeps the original name) +/// - `body` is the expression where the imports are in scope +/// +/// Unlike other forms, `use` does not produce a dedicated AST node. +/// It resolves the imports into the namespace, visits the body with +/// those imports available, and returns the body directly. +pub(super) fn lower_use<'heap, S>( + expander: &mut Expander<'_, 'heap, S>, + CallExpr { + id: _, + span, + function: _, + arguments, + labeled_arguments, + }: &mut CallExpr<'heap>, +) -> Expr<'heap> +where + S: BumpAllocator, +{ + if !labeled_arguments.is_empty() { + expander + .diagnostics + .push(error::labeled_arguments_in_use(labeled_arguments)); + } + + if let [path, imports, body] = &mut **arguments { + lower_use_impl(expander, path, imports, body) + } else { + expander + .diagnostics + .push(error::invalid_use_argument_count(*span, arguments)); + + Expr::dummy() + } +} diff --git a/libs/@local/hashql/ast/src/lower/mod.rs b/libs/@local/hashql/ast/src/lower/mod.rs new file mode 100644 index 00000000000..800c217fd33 --- /dev/null +++ b/libs/@local/hashql/ast/src/lower/mod.rs @@ -0,0 +1,145 @@ +//! AST lowering pipeline. +//! +//! Transforms a parsed HashQL AST into a form suitable for HIR construction +//! by running a sequence of passes that modify the tree in place. +//! +//! # Pipeline +//! +//! The [`lower`] function runs these passes in order: +//! +//! 1. [`Expander`] resolves names, expands special forms, and resolves imports in a single top-down +//! traversal. +//! 2. [`Sanitizer`] validates the expanded tree (e.g. rejects bare special form references that +//! survived expansion). +//! 3. [`NameMangler`] rewrites user-facing names into internal mangled forms. +//! 4. [`TypeDefinitionExtractor`] collects named type definitions. +//! 5. [`NodeRenumberer`] assigns unique IDs to every AST node. +//! 6. [`TypeExtractor`] collects anonymous types and closure signatures. +//! +//! Each pass may emit diagnostics. The pipeline continues through all passes +//! and returns the union of all diagnostics, so the user sees every error at +//! once rather than one pass at a time. +//! +//! [`Expander`]: expander::Expander +//! [`Sanitizer`]: sanitizer::Sanitizer +//! [`NameMangler`]: name_mangler::NameMangler +//! [`TypeDefinitionExtractor`]: type_extractor::TypeDefinitionExtractor +//! [`NodeRenumberer`]: node_renumberer::NodeRenumberer +//! [`TypeExtractor`]: type_extractor::TypeExtractor + +use hashql_core::{ + heap::ResetAllocator, + module::{ModuleRegistry, locals::TypeLocals, namespace::ModuleNamespace}, + span::SpanId, + symbol::Symbol, + r#type::environment::Environment, +}; +use hashql_diagnostics::{DiagnosticIssues, Status, StatusExt as _}; + +use self::{ + error::LoweringDiagnosticCategory, + expander::Expander, + name_mangler::NameMangler, + node_renumberer::NodeRenumberer, + sanitizer::Sanitizer, + type_extractor::{AnonymousTypes, ClosureSignatures, TypeDefinitionExtractor, TypeExtractor}, +}; +use crate::{node::expr::Expr, visit::Visitor as _}; + +pub mod error; +pub mod expander; +pub mod name_mangler; +pub mod node_renumberer; +pub mod sanitizer; +pub mod type_extractor; + +/// Type information collected by the lowering pipeline. +#[derive(Debug)] +pub struct ExtractedTypes<'heap> { + /// Named type definitions local to the current module. + pub locals: TypeLocals<'heap>, + + /// Anonymous types from type expressions (tuples, unions, generics, etc.). + pub anonymous: AnonymousTypes, + + /// Closure signatures from closure expressions. + pub signatures: ClosureSignatures<'heap>, +} + +/// Runs the full lowering pipeline on `expr`, modifying it in place. +/// +/// Returns the collected [`ExtractedTypes`] on success. All passes run +/// regardless of earlier errors so that the returned diagnostics cover +/// the entire tree. +/// +/// # Errors +/// +/// Returns a [`Status`] containing diagnostics from any pass that failed: +/// +/// - [`LoweringDiagnosticCategory::Expander`]: name resolution, special form expansion, or import +/// resolution errors. +/// - [`LoweringDiagnosticCategory::Sanitizer`]: invalid AST constructs that survived expansion. +/// - [`LoweringDiagnosticCategory::Extractor`]: type extraction errors. +pub fn lower<'heap, S>( + module_name: Symbol<'heap>, + expr: &mut Expr<'heap>, + env: &Environment<'heap>, + registry: &ModuleRegistry<'heap>, + mut scratch: S, +) -> Status, LoweringDiagnosticCategory, SpanId> +where + S: ResetAllocator, +{ + let mut diagnostics = DiagnosticIssues::new(); + + let mut namespace = ModuleNamespace::new(registry); + namespace.import_prelude(); + + let mut expander = Expander::new(namespace, &mut scratch); + expander.visit_expr(expr); + diagnostics.append( + &mut expander + .take_diagnostics() + .map_category(LoweringDiagnosticCategory::Expander), + ); + scratch.reset(); + + let mut sanitizer = Sanitizer::new(); + sanitizer.visit_expr(expr); + diagnostics.append( + &mut sanitizer + .take_diagnostics() + .map_category(LoweringDiagnosticCategory::Sanitizer), + ); + + let mut mangler = NameMangler::new(env.heap); + mangler.visit_expr(expr); + + let mut extractor = TypeDefinitionExtractor::new(env, registry, module_name); + extractor.visit_expr(expr); + let (named_types, extractor_diagnostics) = extractor.finish(); + diagnostics + .append(&mut extractor_diagnostics.map_category(LoweringDiagnosticCategory::Extractor)); + + let mut renumberer = NodeRenumberer::new(); + renumberer.visit_expr(expr); + + let mut extractor = TypeExtractor::new(env, registry, &named_types); + extractor.visit_expr(expr); + diagnostics.append( + &mut extractor + .take_diagnostics() + .map_category(LoweringDiagnosticCategory::Extractor), + ); + let (anonymous_types, closure_signatures) = extractor.into_types(); + + let types = ExtractedTypes { + locals: named_types, + anonymous: anonymous_types, + signatures: closure_signatures, + }; + + let mut result = Status::success(types); + result.append_diagnostics(&mut diagnostics); + result +} diff --git a/libs/@local/hashql/ast/src/lowering/name_mangler.rs b/libs/@local/hashql/ast/src/lower/name_mangler.rs similarity index 100% rename from libs/@local/hashql/ast/src/lowering/name_mangler.rs rename to libs/@local/hashql/ast/src/lower/name_mangler.rs diff --git a/libs/@local/hashql/ast/src/lowering/node_renumberer.rs b/libs/@local/hashql/ast/src/lower/node_renumberer.rs similarity index 100% rename from libs/@local/hashql/ast/src/lowering/node_renumberer.rs rename to libs/@local/hashql/ast/src/lower/node_renumberer.rs diff --git a/libs/@local/hashql/ast/src/lowering/sanitizer.rs b/libs/@local/hashql/ast/src/lower/sanitizer.rs similarity index 100% rename from libs/@local/hashql/ast/src/lowering/sanitizer.rs rename to libs/@local/hashql/ast/src/lower/sanitizer.rs diff --git a/libs/@local/hashql/ast/src/lowering/type_extractor/contractive.rs b/libs/@local/hashql/ast/src/lower/type_extractor/contractive.rs similarity index 100% rename from libs/@local/hashql/ast/src/lowering/type_extractor/contractive.rs rename to libs/@local/hashql/ast/src/lower/type_extractor/contractive.rs diff --git a/libs/@local/hashql/ast/src/lowering/type_extractor/definition.rs b/libs/@local/hashql/ast/src/lower/type_extractor/definition.rs similarity index 98% rename from libs/@local/hashql/ast/src/lowering/type_extractor/definition.rs rename to libs/@local/hashql/ast/src/lower/type_extractor/definition.rs index 992df125044..151604b7949 100644 --- a/libs/@local/hashql/ast/src/lowering/type_extractor/definition.rs +++ b/libs/@local/hashql/ast/src/lower/type_extractor/definition.rs @@ -101,10 +101,7 @@ impl<'env, 'heap> TypeDefinitionExtractor<'env, 'heap> { let mut diagnostics = DiagnosticIssues::new(); // Setup the translation unit and environment - let mut locals = FastHashMap::with_capacity_and_hasher( - self.alias.len() + self.opaque.len(), - foldhash::fast::RandomState::default(), - ); + let mut locals = fast_hash_map_with_capacity(self.alias.len() + self.opaque.len()); let mut allocated_generic_constraints = 0; // This could be easier if we were to use a `FastHashMap` for the alias and opaque @@ -305,7 +302,6 @@ impl<'heap> Visitor<'heap> for TypeDefinitionExtractor<'_, 'heap> { | ExprKind::Literal(_) | ExprKind::Path(_) | ExprKind::Let(_) - | ExprKind::Use(_) | ExprKind::Input(_) | ExprKind::Closure(_) | ExprKind::If(_) diff --git a/libs/@local/hashql/ast/src/lowering/type_extractor/error.rs b/libs/@local/hashql/ast/src/lower/type_extractor/error.rs similarity index 100% rename from libs/@local/hashql/ast/src/lowering/type_extractor/error.rs rename to libs/@local/hashql/ast/src/lower/type_extractor/error.rs diff --git a/libs/@local/hashql/ast/src/lowering/type_extractor/mod.rs b/libs/@local/hashql/ast/src/lower/type_extractor/mod.rs similarity index 95% rename from libs/@local/hashql/ast/src/lowering/type_extractor/mod.rs rename to libs/@local/hashql/ast/src/lower/type_extractor/mod.rs index 5827bdb89ea..1a67aad1f42 100644 --- a/libs/@local/hashql/ast/src/lowering/type_extractor/mod.rs +++ b/libs/@local/hashql/ast/src/lower/type_extractor/mod.rs @@ -7,7 +7,7 @@ use alloc::borrow::Cow; use core::ops::Index; use hashql_core::{ - collections::FastHashMap, + collections::{FastHashMap, FastHashMapIntoIter}, module::{ ModuleRegistry, locals::{TypeDef, TypeLocals}, @@ -45,7 +45,7 @@ impl Index for AnonymousTypes { } impl IntoIterator for AnonymousTypes { - type IntoIter = hashbrown::hash_map::IntoIter; + type IntoIter = FastHashMapIntoIter; type Item = (NodeId, TypeId); fn into_iter(self) -> Self::IntoIter { @@ -65,7 +65,7 @@ impl<'heap> Index for ClosureSignatures<'heap> { } impl<'heap> IntoIterator for ClosureSignatures<'heap> { - type IntoIter = hashbrown::hash_map::IntoIter>; + type IntoIter = FastHashMapIntoIter>; type Item = (NodeId, TypeDef<'heap>); fn into_iter(self) -> Self::IntoIter { diff --git a/libs/@local/hashql/ast/src/lowering/type_extractor/translate.rs b/libs/@local/hashql/ast/src/lower/type_extractor/translate.rs similarity index 98% rename from libs/@local/hashql/ast/src/lowering/type_extractor/translate.rs rename to libs/@local/hashql/ast/src/lower/type_extractor/translate.rs index 6d025276148..f7f29b322a2 100644 --- a/libs/@local/hashql/ast/src/lowering/type_extractor/translate.rs +++ b/libs/@local/hashql/ast/src/lower/type_extractor/translate.rs @@ -8,7 +8,10 @@ use alloc::borrow::Cow; use hashql_core::{ - collections::{FastHashMap, FastHashSet, SmallVec, TinyVec, fast_hash_set_with_capacity}, + collections::{ + FastHashMap, FastHashSet, SmallVec, TinyVec, fast_hash_map_with_capacity, + fast_hash_set_with_capacity, + }, intern::Provisioned, module::{ ModuleRegistry, Universe, @@ -35,7 +38,7 @@ use super::error::{ invalid_resolved_item, resolution_error, unknown_intrinsic_type, unused_generic_parameter, }; use crate::{ - lowering::type_extractor::error::{ + lower::type_extractor::error::{ generic_parameter_mismatch, intrinsic_parameter_count_mismatch, unbound_type_variable, }, node::{ @@ -404,15 +407,15 @@ where fn intrinsic( &mut self, span: SpanId, - name: &'static str, + name: Symbol<'heap>, parameters: &[PathSegmentArgument<'heap>], ) -> TypeKind<'heap> { - match name { + match name.as_str() { "::kernel::type::List" => { if parameters.len() != 1 { self.diagnostics.push(intrinsic_parameter_count_mismatch( span, - name, + name.as_str(), 1, parameters.len(), )); @@ -432,7 +435,7 @@ where if parameters.len() != 2 { self.diagnostics.push(intrinsic_parameter_count_mismatch( span, - name, + name.as_str(), 2, parameters.len(), )); @@ -458,7 +461,7 @@ where self.diagnostics.push(unknown_intrinsic_type( span, self.env.heap, - name, + name.as_str(), &["::kernel::type::List", "::kernel::type::Dict"], )); @@ -640,10 +643,7 @@ where node::r#type::TypeKind::Struct(struct_type) => { let mut fields = SmallVec::with_capacity(struct_type.fields.len()); - let mut spans = FastHashMap::with_capacity_and_hasher( - fields.len(), - foldhash::fast::RandomState::default(), - ); + let mut spans = fast_hash_map_with_capacity(fields.len()); for node::r#type::StructField { name, r#type, span, .. diff --git a/libs/@local/hashql/ast/src/lowering/import_resolver/error.rs b/libs/@local/hashql/ast/src/lowering/import_resolver/error.rs deleted file mode 100644 index 7e3c0e1476f..00000000000 --- a/libs/@local/hashql/ast/src/lowering/import_resolver/error.rs +++ /dev/null @@ -1,712 +0,0 @@ -use alloc::borrow::Cow; -use core::{ - fmt::{self, Display, Write as _}, - iter, -}; - -use hashql_core::{ - algorithms::did_you_mean, - collections::FastHashSet, - module::{ - ModuleRegistry, Universe, - error::{ResolutionError, ResolutionSuggestion}, - import::Import, - item::Item, - }, - span::SpanId, - symbol::{Ident, Symbol}, -}; -use hashql_diagnostics::{ - Diagnostic, DiagnosticIssues, Label, - category::{DiagnosticCategory, TerminalDiagnosticCategory}, - diagnostic::Message, - severity::Severity, -}; - -use crate::node::path::Path; - -pub(crate) type ImportResolverDiagnostic = Diagnostic; - -pub(crate) type ImportResolverDiagnosticIssues = - DiagnosticIssues; - -const GENERIC_ARGUMENTS_IN_USE_PATH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "generic-arguments-in-use-path", - name: "Generic arguments not allowed in import paths", -}; - -const EMPTY_PATH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "empty-path", - name: "Empty path in import statement", -}; - -const GENERIC_ARGUMENTS_IN_MODULE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "generic-arguments-in-module", - name: "Generic arguments only allowed in final path segment", -}; - -const UNRESOLVED_IMPORT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "unresolved-import", - name: "Unresolved import", -}; - -const UNRESOLVED_VARIABLE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "unresolved-variable", - name: "Unresolved variable", -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum ImportResolverDiagnosticCategory { - GenericArgumentsInUsePath, - EmptyPath, - GenericArgumentsInModule, - UnresolvedImport, - UnresolvedVariable, -} - -impl DiagnosticCategory for ImportResolverDiagnosticCategory { - fn id(&self) -> Cow<'_, str> { - Cow::Borrowed("import-resolver") - } - - fn name(&self) -> Cow<'_, str> { - Cow::Borrowed("Import Resolver") - } - - fn subcategory(&self) -> Option<&dyn DiagnosticCategory> { - match self { - Self::GenericArgumentsInUsePath => Some(&GENERIC_ARGUMENTS_IN_USE_PATH), - Self::EmptyPath => Some(&EMPTY_PATH), - Self::GenericArgumentsInModule => Some(&GENERIC_ARGUMENTS_IN_MODULE), - Self::UnresolvedImport => Some(&UNRESOLVED_IMPORT), - Self::UnresolvedVariable => Some(&UNRESOLVED_VARIABLE), - } - } -} - -/// Error when generic arguments are used in a use path segment. -pub(crate) fn generic_arguments_in_use_path( - span: SpanId, - use_span: SpanId, -) -> ImportResolverDiagnostic { - let mut diagnostic = Diagnostic::new( - ImportResolverDiagnosticCategory::GenericArgumentsInUsePath, - Severity::Bug, - ) - .primary(Label::new(span, "Remove these generic arguments")); - - diagnostic - .labels - .push(Label::new(use_span, "In this import statement")); - - diagnostic.add_message(Message::help( - "Use statements don't accept generic type parameters. Remove the angle brackets and type \ - parameters.\n\nExample: Use `module::Type` instead of `module::Type`.", - )); - - diagnostic.add_message(Message::note( - "This error is still valid, but should've been caught in an earlier stage of the compiler \ - pipeline.", - )); - - diagnostic -} - -/// Error when a path has no segments. -pub(crate) fn empty_path(span: SpanId) -> ImportResolverDiagnostic { - let mut diagnostic = - Diagnostic::new(ImportResolverDiagnosticCategory::EmptyPath, Severity::Bug) - .primary(Label::new(span, "Specify a path here")); - - diagnostic.add_message(Message::help( - "Add a valid path with at least one identifier, such as `module` or `module::item`.", - )); - - diagnostic.add_message(Message::note( - "Import statements require a non-empty path to identify what module or item you want to \ - bring into scope. This error is still valid, but should've been caught in an earlier \ - stage of the compiler pipeline.", - )); - - diagnostic -} - -/// Error when generic arguments are used in a module path segment. -pub(crate) fn generic_arguments_in_module( - spans: impl IntoIterator, -) -> ImportResolverDiagnostic { - let mut spans = spans.into_iter(); - let primary = spans.next().expect("spans should be non-empty"); - - let mut diagnostic = Diagnostic::new( - ImportResolverDiagnosticCategory::GenericArgumentsInModule, - Severity::Error, - ) - .primary(Label::new(primary, "Remove this generic argument")); - - for secondary in spans { - diagnostic - .labels - .push(Label::new(secondary, "... and this generic argument")); - } - - diagnostic.add_message(Message::help( - "Generic arguments can only appear on the final type in a path. Remove them from this \ - module segment or move them to the final type in the path.\n\nCorrect: \ - `module::submodule::Type`\nIncorrect: `module::submodule::Type`", - )); - - diagnostic.add_message(Message::note( - "Module paths don't accept generic parameters because modules themselves aren't generic. \ - Only the final type in a path can have generic parameters.\n\nThe path resolution \ - happens before any generic type checking, so generic arguments can only be applied after \ - the item is found.", - )); - - diagnostic -} - -fn format_suggestions<'heap, T>( - name: Symbol<'heap>, - suggestions: &[ResolutionSuggestion<'heap, T>], -) -> Option { - if suggestions.is_empty() { - return None; - } - - let max_suggestions = match suggestions.len() { - 0 => return None, - 1..=3 => suggestions.len(), - 4..=8 => 4, - _ => 5, - }; - - let good_suggestions = did_you_mean( - name, - suggestions - .iter() - .map(|ResolutionSuggestion { name, .. }| *name), - Some(max_suggestions), - None, - ); - - if good_suggestions.is_empty() { - return None; - } - - let mut result = String::from("\n\nDid you mean:"); - for suggestion in &good_suggestions { - write!(result, "\n - {suggestion}").unwrap_or_else(|_err| unreachable!()); - } - - let remaining_count = suggestions.len().saturating_sub(good_suggestions.len()); - match remaining_count { - 0 => {} - 1 => result.push_str("\n(1 more available)"), - n => write!(result, "\n({n} more available)").unwrap_or_else(|_err| unreachable!()), - } - - Some(result) -} - -struct FormatPath<'a, 'heap>(bool, &'a [(SpanId, Symbol<'heap>)], Option); - -impl Display for FormatPath<'_, '_> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let &Self(rooted, segments, depth) = self; - - if rooted { - fmt.write_str("::")?; - } - - let segments = depth.map_or(segments, |depth| &segments[..=depth]); - - for (index, (_, segment)) in segments.iter().enumerate() { - if index > 0 { - fmt.write_str("::")?; - } - - Display::fmt(segment, fmt)?; - } - - Ok(()) - } -} - -pub(crate) fn unresolved_variable<'heap>( - registry: &ModuleRegistry<'heap>, - universe: Universe, - ident: Ident<'heap>, - locals: &FastHashSet>, - mut suggestions: Vec>>, -) -> ImportResolverDiagnostic { - let mut diagnostic = Diagnostic::new( - ImportResolverDiagnosticCategory::UnresolvedVariable, - Severity::Error, - ) - .primary(Label::new( - ident.span, - format!("Cannot find variable '{}'", ident.value), - )); - - // Filter out suggestions that are already available locally - suggestions.retain(|suggestion| !locals.contains(&suggestion.name)); - - let help = build_unresolved_variable_help(registry, universe, ident, locals, &suggestions); - diagnostic.add_message(Message::help(help)); - - diagnostic.add_message(Message::note( - "Variables must be defined before they can be used. This could be a typo, a variable used \ - outside its scope, or a missing declaration. If it's a function or type from another \ - module, you might need to import it first.", - )); - - diagnostic -} - -fn build_unresolved_variable_help<'heap>( - registry: &ModuleRegistry<'heap>, - universe: Universe, - ident: Ident<'heap>, - locals: &FastHashSet>, - suggestions: &[ResolutionSuggestion>], -) -> String { - let mut help = format!("The name '{}' doesn't exist in this scope.", ident.value); - - let has_locals = add_local_suggestions(&mut help, ident.value, locals); - let has_imports = add_import_suggestions(&mut help, ident.value, suggestions, has_locals); - add_available_imports( - &mut help, - registry, - universe, - ident, - locals, - suggestions, - has_locals || has_imports, - ); - - help -} - -fn add_local_suggestions( - help: &mut String, - name: Symbol<'_>, - locals: &FastHashSet>, -) -> bool { - let local_suggestions = did_you_mean(name, locals, Some(5), None); - if local_suggestions.is_empty() { - return false; - } - - help.push_str("\n\nDid you mean one of these local variables:"); - for suggestion in &local_suggestions { - let _: fmt::Result = write!(help, "\n - {suggestion}"); - } - - let remaining = locals.len().saturating_sub(local_suggestions.len()); - if remaining > 0 { - let _: fmt::Result = write!(help, "\n ({remaining} more available)"); - } - - true -} - -fn add_import_suggestions( - help: &mut String, - name: Symbol<'_>, - suggestions: &[ResolutionSuggestion>], - has_local_suggestions: bool, -) -> bool { - if suggestions.is_empty() { - return false; - } - - let good_suggestions = did_you_mean( - name, - suggestions.iter().map(|suggestion| suggestion.name), - Some(5), - None, - ); - - if good_suggestions.is_empty() { - return false; - } - - if has_local_suggestions { - help.push_str("\n\nOr perhaps you meant one of these imported items:"); - } else { - help.push_str("\n\nDid you mean one of these imported items:"); - } - for suggestion in &good_suggestions { - let _: fmt::Result = write!(help, "\n - {suggestion}"); - } - - let remaining = suggestions.len().saturating_sub(good_suggestions.len()); - if remaining > 0 { - let _: fmt::Result = write!(help, "\n ({remaining} more available)"); - } - - true -} - -fn add_available_imports<'heap>( - help: &mut String, - registry: &ModuleRegistry<'heap>, - universe: Universe, - ident: Ident<'heap>, - locals: &FastHashSet>, - suggestions: &[ResolutionSuggestion>], - has_previous: bool, -) { - let imported_names: FastHashSet<_> = suggestions - .iter() - .map(|suggestion| suggestion.name) - .collect(); - - let importable: Vec<_> = registry - .search_by_name(ident.value, universe) - .into_iter() - .filter(|item| !locals.contains(&item.name) && !imported_names.contains(&item.name)) - .collect(); - - if importable.is_empty() { - return; - } - - if has_previous { - help.push_str("\n\nAdditionally, items with a similar name exist in other modules:"); - } else { - help.push_str("\n\nItems with a similar name exist in other modules:"); - } - - for item in importable.iter().take(5) { - let absolute_path = format_absolute_path(item, registry); - let _: fmt::Result = write!(help, "\n - {absolute_path}"); - } - - if importable.len() > 5 { - let remaining = importable.len() - 5; - let _: fmt::Result = write!(help, "\n ({remaining} more available)"); - } - - // Show usage example for the first item - if let Some(item) = importable.first() { - let absolute_path = format_absolute_path(item, registry); - help.push_str("\n\nTo use an item, you can either:"); - let _: fmt::Result = write!(help, "\n 1. Import it: use {absolute_path} in"); - let _: fmt::Result = write!(help, "\n 2. Use fully qualified path: {absolute_path}"); - } -} - -fn format_absolute_path<'heap>(item: &Item<'heap>, registry: &ModuleRegistry<'heap>) -> String { - iter::once("") - .chain(item.absolute_path(registry).map(Symbol::unwrap)) - .intersperse("::") - .collect() -} - -#[expect(clippy::too_many_lines)] -pub(crate) fn from_resolution_error<'heap>( - use_span: Option, - path: &Path<'heap>, - name: Option<(SpanId, Symbol<'heap>)>, - error: ResolutionError<'heap>, -) -> ImportResolverDiagnostic { - let diagnostic = Diagnostic::new( - ImportResolverDiagnosticCategory::UnresolvedImport, - Severity::Error, - ); - - let segments: Vec<_> = path - .segments - .iter() - .map(|segment| (segment.span, segment.name.value)) - .chain(name) - .collect(); - - match error { - ResolutionError::InvalidQueryLength { expected } => { - let mut diagnostic = diagnostic.primary( - // Primary label highlighting the problematic path - Label::new(path.span, "Expected more path segments"), - ); - - if let Some(use_span) = use_span { - // Secondary label showing the context of the use statement - diagnostic - .labels - .push(Label::new(use_span, "In this import statement")); - } - - diagnostic.add_message(Message::help(format!( - "This import path needs at least {expected} segments to be valid. Add the missing \ - segments to complete the path." - ))); - - diagnostic.add_message(Message::note( - "Import paths must be complete to properly identify the item you want to import. \ - Incomplete paths cannot be resolved.", - )); - - diagnostic - } - - ResolutionError::ModuleRequired { depth, found } => { - let (path_segment_span, _) = segments[depth]; - - let mut diagnostic = diagnostic.primary(Label::new( - path_segment_span, - format!( - "'{}' cannot contain other items", - FormatPath(path.rooted, &segments, Some(depth)) - ), - )); - - diagnostic - .labels - .push(Label::new(path.span, "In this path")); - - let universe = match found { - Some(Universe::Value) => "a value", - Some(Universe::Type) => "a type", - None => "not a module", - }; - - diagnostic.add_message(Message::help(format!( - "'{}' is {universe}. Only modules can contain other items. Check your import path.", - FormatPath(path.rooted, &segments, Some(depth)) - ))); - - diagnostic.add_message(Message::note( - "The '::' syntax can only be used with modules to access their members. Values \ - and types cannot contain other items.", - )); - - diagnostic - } - - ResolutionError::PackageNotFound { - depth, - name, - suggestions, - } => { - let (package_span, package_name) = segments[depth]; - - let mut diagnostic = diagnostic.primary( - // Primary label highlighting the missing package - Label::new(package_span, format!("Missing package '{package_name}'")), - ); - - if let Some(use_span) = use_span { - diagnostic.labels.push( - // Secondary label showing the context - Label::new(use_span, "In this import statement"), - ); - } - - let mut help = "This package couldn't be found. Make sure it is spelled correctly and \ - installed." - .to_owned(); - - if let Some(suggestion) = format_suggestions(name, &suggestions) { - help.push_str(&suggestion); - } - - diagnostic.add_message(Message::help(help)); - - diagnostic.add_message(Message::note( - "Packages must be installed and properly configured in your project dependencies \ - before they can be imported.", - )); - - diagnostic - } - - ResolutionError::ImportNotFound { - depth, - name, - suggestions, - } => { - let (import_span, import_name) = segments[depth]; - - let mut diagnostic = diagnostic.primary(Label::new( - import_span, - format!("'{import_name}' needs to be imported first"), - )); - - if let Some(use_span) = use_span { - diagnostic.labels.push( - // Add a secondary label for context - Label::new(use_span, "In this import statement"), - ); - } - - let mut help = format!( - "Add an import statement for '{import_name}' before using it. Check if the name \ - is spelled correctly." - ); - - if let Some(suggestion) = format_suggestions(name, &suggestions) { - help.push_str(&suggestion); - } - - diagnostic.add_message(Message::help(help)); - - diagnostic.add_message(Message::note( - "Before using an item from another module, you must import it with a 'use' \ - statement or access it with a fully qualified path.", - )); - - diagnostic - } - - ResolutionError::ModuleNotFound { - depth, - name, - suggestions, - } => { - let (module_span, module_name) = segments[depth]; - - let mut diagnostic = diagnostic.primary(Label::new( - module_span, - format!("Module '{module_name}' not found"), - )); - - diagnostic - .labels - .push(Label::new(path.span, "In this path")); - - let mut help = format!( - "The module '{}' doesn't exist in this scope. Check the spelling and ensure the \ - module is available.", - FormatPath(path.rooted, &segments, Some(depth)) - ); - - if let Some(suggestion) = format_suggestions(name, &suggestions) { - help.push_str(&suggestion); - } - - diagnostic.add_message(Message::help(help)); - - diagnostic.add_message(Message::note( - "Modules must be properly defined and exported from their parent module to be \ - accessible.", - )); - - diagnostic - } - - ResolutionError::ItemNotFound { - depth, - name, - suggestions, - } => { - let (item_span, item_name) = segments[depth]; - - let label_text = if depth == 0 { - format!("'{item_name}' not found in current scope") - } else { - format!( - "'{item_name}' not found in module '{}'", - FormatPath(path.rooted, &segments, Some(depth - 1)) - ) - }; - - let mut diagnostic = diagnostic.primary(Label::new(item_span, label_text)); - - // Add a secondary label highlighting the module - if depth > 0 { - let module_span = segments[depth - 1].0; - diagnostic - .labels - .push(Label::new(module_span, "This module")); - } else if let Some(use_span) = use_span { - diagnostic - .labels - .push(Label::new(use_span, "In this import")); - } else { - // No secondary label needed - neither module nor import context available - } - - let mut help = "Check the spelling and ensure the item is exported and available in \ - this context." - .to_owned(); - - if let Some(suggestion) = format_suggestions(name, &suggestions) { - help.push_str(&suggestion); - } - - diagnostic.add_message(Message::help(help)); - - diagnostic.add_message(Message::note( - "Items must be defined and accessible from the importing location. Make sure the \ - item exists and is public.", - )); - - diagnostic - } - - ResolutionError::Ambiguous(item) => { - // Find the span for the ambiguous name in the path - let item_span = segments - .iter() - .find_map(|&(segment_span, segment_name)| { - (segment_name == item.name()).then_some(segment_span) - }) - .unwrap_or(path.span); - - let mut diagnostic = diagnostic.primary(Label::new( - item_span, - format!("'{}' is ambiguous", item.name()), - )); - - diagnostic - .labels - .push(Label::new(path.span, "In this path")); - - diagnostic.add_message(Message::help(format!( - "The name '{}' could refer to multiple different items in {}. Use a fully \ - qualified path to specify which one you want.", - item.name(), - FormatPath(path.rooted, &segments, None) - ))); - - diagnostic.add_message(Message::note( - "When multiple items with the same name are in scope, you must use a fully \ - qualified path to avoid ambiguity. Consider using explicit imports instead of \ - glob imports to prevent name conflicts.", - )); - - diagnostic - } - - ResolutionError::ModuleEmpty { depth } => { - let (module_span, _) = segments[depth]; - - let mut diagnostic = diagnostic.primary(Label::new( - module_span, - format!( - "Module '{}' has no exported members", - FormatPath(path.rooted, &segments, Some(depth)) - ), - )); - - diagnostic - .labels - .push(Label::new(path.span, "In this import path")); - - diagnostic.add_message(Message::help( - "This module exists but doesn't expose any items that can be imported. Check if \ - you're importing the correct module or if the module has any public exports.", - )); - - diagnostic.add_message(Message::note( - "To use items from a module, they must be marked as public/exported. If you're \ - using a glob import pattern like 'module::*', try using specific imports instead \ - to see what's available.", - )); - - diagnostic - } - } -} diff --git a/libs/@local/hashql/ast/src/lowering/import_resolver/mod.rs b/libs/@local/hashql/ast/src/lowering/import_resolver/mod.rs deleted file mode 100644 index c88b2b30a05..00000000000 --- a/libs/@local/hashql/ast/src/lowering/import_resolver/mod.rs +++ /dev/null @@ -1,478 +0,0 @@ -pub mod error; - -use core::{iter, mem}; - -use hashql_core::{ - heap::Heap, - module::{ - Reference, Universe, - error::ResolutionError, - namespace::{ImportOptions, ModuleNamespace, ResolutionMode, ResolveOptions}, - }, - symbol::{Ident, IdentKind, Symbol}, -}; -use hashql_diagnostics::DiagnosticIssues; - -use self::error::{ - ImportResolverDiagnosticIssues, empty_path, from_resolution_error, generic_arguments_in_module, - generic_arguments_in_use_path, unresolved_variable, -}; -use super::super::node::path::PathSegmentArgument; -use crate::{ - node::{ - expr::{ - ClosureExpr, Expr, ExprKind, LetExpr, NewTypeExpr, TypeExpr, UseExpr, - r#use::{UseBinding, UseKind}, - }, - id::NodeId, - path::{Path, PathSegment}, - r#type::{Type, TypeKind}, - }, - visit::{Visitor, walk_closure_expr, walk_expr, walk_path, walk_type}, -}; - -pub struct ImportResolver<'env, 'heap> { - heap: &'heap Heap, - namespace: ModuleNamespace<'env, 'heap>, - current_universe: Universe, - diagnostics: ImportResolverDiagnosticIssues, - handled_diagnostics: usize, -} - -impl<'env, 'heap> ImportResolver<'env, 'heap> { - pub const fn new(heap: &'heap Heap, namespace: ModuleNamespace<'env, 'heap>) -> Self { - Self { - heap, - namespace, - current_universe: Universe::Value, - diagnostics: DiagnosticIssues::new(), - handled_diagnostics: 0, - } - } - - pub fn take_diagnostics(&mut self) -> ImportResolverDiagnosticIssues { - mem::take(&mut self.diagnostics) - } - - const fn critical_diagnostics_count(&self) -> usize { - self.diagnostics.critical() - } - - fn enter( - &mut self, - universe: Universe, - symbol: Symbol<'heap>, - closure: impl FnOnce(&mut Self) -> T, - ) -> T { - let snapshot = self.namespace.snapshot(); - self.namespace.local(symbol, universe); - - let result = closure(self); - - self.namespace.rollback_to(snapshot); - - result - } - - fn enter_many( - &mut self, - universe: Universe, - symbols: impl IntoIterator>, - closure: impl FnOnce(&mut Self) -> T, - ) -> T { - let snapshot = self.namespace.snapshot(); - for symbol in symbols { - self.namespace.local(symbol, universe); - } - - let result = closure(self); - - self.namespace.rollback_to(snapshot); - - result - } -} - -impl<'heap> Visitor<'heap> for ImportResolver<'_, 'heap> { - fn visit_use_expr( - &mut self, - UseExpr { - id: _, - span, - path, - kind, - body, - }: &mut UseExpr<'heap>, - ) { - // We don't need to walk the path here, because the import resolution already does the - // normalization for us - let mut query = Vec::with_capacity(path.segments.len()); - - for segment in &path.segments { - if !segment.arguments.is_empty() { - self.diagnostics - .push(generic_arguments_in_use_path(segment.span, *span)); - } - - query.push(segment.name.value); - } - - let mode = if path.rooted { - ResolutionMode::Absolute - } else { - ResolutionMode::Relative - }; - - let snapshot = self.namespace.snapshot(); - - match kind { - UseKind::Named(use_bindings) => { - for UseBinding { - id: _, - span: _, - name, - alias, - } in use_bindings.drain(..) - { - let alias = alias.map_or(name.value, |alias| alias.value); - - let result = self.namespace.import( - alias, - query.iter().copied().chain(iter::once(name.value)), - ImportOptions { - glob: false, - mode, - suggestions: true, - }, - ); - - if let Err(error) = result { - self.diagnostics.push(from_resolution_error( - Some(*span), - path, - Some((name.span, name.value)), - error, - )); - - // We cannot continue here, so we replace the body with `Dummy`, this way we - // can still report the error and continue in the control flow - **body = Expr::dummy(); - } - } - } - UseKind::Glob(_) => { - let result = self.namespace.import( - self.heap.intern_symbol("*"), - query.iter().copied(), - ImportOptions { - glob: true, - mode, - suggestions: true, - }, - ); - - if let Err(error) = result { - self.diagnostics - .push(from_resolution_error(Some(*span), path, None, error)); - - // We cannot continue here, so we replace the body with `Dummy`, this way we - // can still report the error and continue in the control flow - **body = Expr::dummy(); - } - } - } - - self.visit_expr(body); - - self.namespace.rollback_to(snapshot); - } - - fn visit_path(&mut self, path: &mut Path<'heap>) { - let [modules @ .., ident] = &*path.segments else { - self.diagnostics.push(empty_path(path.span)); - return; - }; - - // We don't support generics except for the *last* segment - let mut should_continue = true; - for module in modules { - if !module.arguments.is_empty() { - self.diagnostics.push(generic_arguments_in_module( - module.arguments.iter().map(PathSegmentArgument::span), - )); - - should_continue = false; - } - } - - if !should_continue { - // While in theory we could continue processing here, the problem would be that any - // generic parameter would double emit errors, which adds additional visual noise. - return; - } - - let segments = path.segments.iter().map(|segment| segment.name.value); - - let mode = if path.rooted { - ResolutionMode::Absolute - } else { - ResolutionMode::Relative - }; - - let reference = match self.namespace.resolve( - segments, - ResolveOptions { - mode, - universe: self.current_universe, - }, - ) { - Ok(item) => item, - Err(ResolutionError::ImportNotFound { - depth: _, - name: _, - suggestions, - }) if modules.is_empty() => { - self.diagnostics.push(unresolved_variable( - self.namespace.registry(), - self.current_universe, - ident.name, - &self.namespace.locals(self.current_universe), - suggestions, - )); - - walk_path(self, path); - return; - } - Err(error) => { - self.diagnostics - .push(from_resolution_error(None, path, None, error)); - - walk_path(self, path); - return; - } - }; - - let item = match reference { - Reference::Binding(_) => { - walk_path(self, path); - return; - } - Reference::Item(item) => item, - }; - - let segments: Vec<_> = item.absolute_path(self.namespace.registry()).collect(); - - // The trailing segments might not be the same due to renames, reset the symbol to the - // canonical form (but retain the span) - debug_assert!(segments.len() >= path.segments.len()); - - // For the trailing segments, set the name to the canonical name (they might be renamed) - for (&lhs, rhs) in segments[segments.len() - path.segments.len()..] - .iter() - .zip(&mut path.segments) - { - rhs.name.value = lhs; - } - - let span = path.segments.first().unwrap_or_else(|| unreachable!()).span; - - path.rooted = true; - path.segments.splice( - 0..0, - segments[..segments.len() - path.segments.len()] - .iter() - .map(|&ident| PathSegment { - id: NodeId::PLACEHOLDER, - span, - name: Ident { - span, - value: ident, - kind: IdentKind::Lexical, - }, - arguments: Vec::new_in(self.heap), - }), - ); - - walk_path(self, path); - } - - fn visit_expr(&mut self, expr: &mut Expr<'heap>) { - // Process the node first - walk_expr(self, expr); - - match &mut expr.kind { - // Replace any use statement with it's body - ExprKind::Use(use_expr) => { - let inner = mem::replace(&mut *use_expr.body, Expr::dummy()); - *expr = inner; - } - // Replace any path, which has had diagnostics emitted with a dummy expression - kind @ ExprKind::Path(_) => { - let fatal = self.critical_diagnostics_count(); - if self.handled_diagnostics < fatal { - *kind = ExprKind::Dummy; - self.handled_diagnostics = fatal; - } - } - ExprKind::Call(_) - | ExprKind::Struct(_) - | ExprKind::Dict(_) - | ExprKind::Tuple(_) - | ExprKind::List(_) - | ExprKind::Literal(_) - | ExprKind::Let(_) - | ExprKind::Type(_) - | ExprKind::NewType(_) - | ExprKind::Input(_) - | ExprKind::Closure(_) - | ExprKind::If(_) - | ExprKind::Field(_) - | ExprKind::Index(_) - | ExprKind::As(_) - | ExprKind::Underscore - | ExprKind::Dummy => {} - } - } - - fn visit_type(&mut self, r#type: &mut Type<'heap>) { - let previous = self.current_universe; - - self.current_universe = Universe::Type; - walk_type(self, r#type); - self.current_universe = previous; - - // Replace any path, which has had diagnostics emitted with a dummy expression - if matches!(r#type.kind, TypeKind::Path(_)) { - let fatal = self.critical_diagnostics_count(); - if self.handled_diagnostics < fatal { - r#type.kind = TypeKind::Dummy; - self.handled_diagnostics = fatal; - } - } - } - - fn visit_let_expr( - &mut self, - LetExpr { - id, - span, - name, - value, - r#type, - body, - }: &mut LetExpr<'heap>, - ) { - self.visit_id(id); - self.visit_span(span); - self.visit_ident(name); - - // Important: The scope only effect in the body, not in the value, as that would allow the - // creation of recursive values - self.visit_expr(value); - - if let Some(r#type) = r#type { - self.visit_type(r#type); - } - - self.enter(Universe::Value, name.value, |this| { - this.visit_expr(body); - }); - } - - fn visit_type_expr( - &mut self, - TypeExpr { - id, - span, - name, - constraints, - value, - body, - }: &mut TypeExpr<'heap>, - ) { - self.visit_id(id); - self.visit_span(span); - self.visit_ident(name); - - self.enter(Universe::Type, name.value, |this| { - let constraint_symbols: Vec<_> = constraints - .iter() - .map(|constraint| constraint.name.value) - .collect(); - - // Constraints are mentioned in the type value, as well as the constraints themselves, - // while the type outlines the value and is bound in the body as well. - this.enter_many(Universe::Type, constraint_symbols, |this| { - for constraint in constraints { - this.visit_generic_constraint(constraint); - } - - this.visit_type(value); - }); - - this.visit_expr(body); - }); - } - - fn visit_newtype_expr( - &mut self, - NewTypeExpr { - id, - span, - name, - constraints, - value, - body, - }: &mut NewTypeExpr<'heap>, - ) { - self.visit_id(id); - self.visit_span(span); - self.visit_ident(name); - - self.enter(Universe::Type, name.value, |this| { - let constraint_symbols: Vec<_> = constraints - .iter() - .map(|constraint| constraint.name.value) - .collect(); - - // Constraints are mentioned in the type value, as well as the constraints themselves, - // while the type outlines the value and is bound in the body as well. - this.enter_many(Universe::Type, constraint_symbols, |this| { - for constraint in constraints { - this.visit_generic_constraint(constraint); - } - - this.visit_type(value); - }); - - // Unlike types, newtypes (opaque types) also bring into scope (only in the body) - // themselves as a constructor - this.enter(Universe::Value, name.value, |this| { - this.visit_expr(body); - }); - }); - } - - fn visit_closure_expr(&mut self, expr: &mut ClosureExpr<'heap>) { - let generic_symbols: Vec<_> = expr - .signature - .generics - .params - .iter() - .map(|param| param.name.value) - .collect(); - - let param_symbols: Vec<_> = expr - .signature - .inputs - .iter() - .map(|input| input.name.value) - .collect(); - - self.enter_many(Universe::Type, generic_symbols, |this| { - this.enter_many(Universe::Value, param_symbols, |this| { - walk_closure_expr(this, expr); - }); - }); - } -} diff --git a/libs/@local/hashql/ast/src/lowering/mod.rs b/libs/@local/hashql/ast/src/lowering/mod.rs deleted file mode 100644 index 45e6554ed10..00000000000 --- a/libs/@local/hashql/ast/src/lowering/mod.rs +++ /dev/null @@ -1,247 +0,0 @@ -//! HashQL AST lowering and transformation pipeline. -//! -//! This module provides the core lowering functionality that transforms parsed HashQL AST nodes -//! into a form suitable for type checking and code generation in the HIR. The lowering process -//! applies multiple transformation passes including name resolution, special form expansion, -//! sanitization, import resolution, name mangling, type extraction, and node renumbering. -//! -//! The lowering pipeline ensures that: -//! - All names are properly resolved and scoped -//! - Special forms and macros are expanded -//! - Code is sanitized for safety and correctness -//! - Type information is extracted and preserved -//! - Nodes are uniquely numbered for analysis -//! -//! # Pipeline Overview -//! -//! The [`lower`] function orchestrates the complete transformation pipeline: -//! -//! 1. **Pre-expansion name resolution** - Resolves names before macro expansion -//! 2. **Special form expansion** - Expands macros and special language constructs -//! 3. **Sanitization** - Applies safety and correctness transformations -//! 4. **Import resolution** - Resolves module imports and builds namespace -//! 5. **Name mangling** - Applies name mangling for code generation -//! 6. **Type definition extraction** - Extracts named type definitions -//! 7. **Node renumbering** - Assigns unique identifiers to AST nodes -//! 8. **Type extraction** - Extracts anonymous types and closure signatures -//! -//! # Examples -//! -//! ```rust -//! use hashql_ast::lowering::lower; -//! use hashql_core::{ -//! r#type::environment::Environment, -//! module::ModuleRegistry, -//! symbol::Symbol, -//! }; -//! -//! # fn example<'heap>(env: &Environment<'heap>, registry: &ModuleRegistry<'heap>, mut expr: hashql_ast::node::expr::Expr<'heap>) { -//! let module_name = env.heap.intern_symbol("my_module"); -//! let result = lower(module_name, &mut expr, env, registry); -//! -//! match result { -//! Ok(extracted_types) => { -//! // Use extracted type information for analysis -//! println!("Extracted {} local types", extracted_types.value.locals.len()); -//! } -//! Err(diagnostics) => { -//! // Handle lowering errors -//! eprintln!("Lowering failed with {} errors", diagnostics.into_issues().len()); -//! } -//! } -//! # } -//! ``` - -use hashql_core::{ - module::{ModuleRegistry, locals::TypeLocals, namespace::ModuleNamespace}, - span::SpanId, - symbol::Symbol, - r#type::environment::Environment, -}; -use hashql_diagnostics::{DiagnosticIssues, Status, StatusExt as _}; - -use self::{ - error::LoweringDiagnosticCategory, - import_resolver::ImportResolver, - name_mangler::NameMangler, - node_renumberer::NodeRenumberer, - pre_expansion_name_resolver::PreExpansionNameResolver, - sanitizer::Sanitizer, - special_form_expander::SpecialFormExpander, - type_extractor::{AnonymousTypes, ClosureSignatures, TypeDefinitionExtractor, TypeExtractor}, -}; -use crate::{node::expr::Expr, visit::Visitor as _}; - -pub mod error; -pub mod import_resolver; -pub mod name_mangler; -pub mod node_renumberer; -pub mod pre_expansion_name_resolver; -pub mod sanitizer; -pub mod special_form_expander; -pub mod type_extractor; - -/// Type information extracted during the lowering process. -/// -/// This structure contains all the type-related information that is discovered and extracted -/// during the AST lowering pipeline. It includes locally defined types, anonymous types -/// (such as tuple types or array types), and closure signatures. -/// -/// The extracted types are used by subsequent compilation phases for type checking, -/// inference, and code generation. -#[derive(Debug)] -pub struct ExtractedTypes<'heap> { - /// Named type definitions local to the current module. - /// - /// These are types that have been explicitly defined in the source code with names, - /// such as struct definitions, enum definitions, and type aliases. - pub locals: TypeLocals<'heap>, - - /// Anonymous types discovered during lowering. - /// - /// These include types that don't have explicit names in the source code but are - /// constructed from type expressions, such as tuple types, array types, function - /// types, and generic instantiations. - pub anonymous: AnonymousTypes, - - /// Closure signatures extracted from closure expressions. - /// - /// Contains type signature information for all closure expressions found in the - /// AST, including parameter types, return types, and capture information. - pub signatures: ClosureSignatures<'heap>, -} - -/// Performs the complete AST lowering transformation pipeline. -/// -/// This function orchestrates all the transformation passes required to convert a parsed HashQL AST -/// into a form suitable for type checking and code generation. The lowering process is destructive -/// – it modifies the provided AST in place while extracting type information and collecting -/// diagnostics. -/// -/// The function applies transformations in a specific order to ensure correctness: -/// each pass may depend on the results of previous passes, and the order is carefully -/// designed to handle dependencies between different kinds of analysis and transformation. -/// -/// # Arguments -/// -/// - `module_name` - The symbolic name of the module being lowered, used for type resolution. -/// - `expr` - The root expression of the AST to be lowered (modified in place). -/// - `env` - The type environment containing heap allocation and type system context. -/// - `registry` - Registry of available modules for import and name resolution. -/// -/// Returns a [`Status`] containing either the extracted type information on success, or diagnostic -/// issues if the lowering process encounters errors. Even on success, the status may -/// contain warnings or other non-fatal diagnostics. -/// -/// # Examples -/// -/// ```rust -/// use hashql_ast::lowering::lower; -/// use hashql_core::{ -/// r#type::environment::Environment, -/// module::ModuleRegistry, -/// symbol::Symbol, -/// }; -/// # use hashql_diagnostics::{Success, Failure}; -/// -/// # fn example<'heap>(env: &Environment<'heap>, registry: &ModuleRegistry<'heap>, mut expr: hashql_ast::node::expr::Expr<'heap>) { -/// let module_name = env.heap.intern_symbol("my_module"); -/// let result = lower(module_name, &mut expr, env, registry); -/// -/// // Check for successful lowering -/// match result { -/// Ok(Success {value, advisories}) => { -/// // Process the successful result -/// println!("Successfully lowered module with {} local types", -/// value.locals.len()); -/// } -/// Err(Failure {primary, secondary}) => { -/// // Handle errors -/// eprintln!("Primary Error: {primary:?}"); -/// -/// for error in secondary { -/// eprintln!("Secondary Error: {error:?}"); -/// } -/// } -/// } -/// # } -/// ``` -/// -/// # Errors -/// -/// The function collects diagnostics from each transformation pass and returns them -/// as part of the [`Status`]. Possible error categories include: -/// -/// - [`LoweringDiagnosticCategory::Expander`] - Errors during special form expansion -/// - [`LoweringDiagnosticCategory::Sanitizer`] - Errors during code sanitization -/// - [`LoweringDiagnosticCategory::Resolver`] - Errors during import resolution -/// - [`LoweringDiagnosticCategory::Extractor`] - Errors during type extraction -pub fn lower<'heap>( - module_name: Symbol<'heap>, - expr: &mut Expr<'heap>, - env: &Environment<'heap>, - registry: &ModuleRegistry<'heap>, -) -> Status, LoweringDiagnosticCategory, SpanId> { - let mut diagnostics = DiagnosticIssues::new(); - - let mut resolver = PreExpansionNameResolver::new(registry); - resolver.visit_expr(expr); - - let mut expander = SpecialFormExpander::new(env.heap); - expander.visit_expr(expr); - diagnostics.append( - &mut expander - .take_diagnostics() - .map_category(LoweringDiagnosticCategory::Expander), - ); - - let mut sanitizer = Sanitizer::new(); - sanitizer.visit_expr(expr); - diagnostics.append( - &mut sanitizer - .take_diagnostics() - .map_category(LoweringDiagnosticCategory::Sanitizer), - ); - - let mut namespace = ModuleNamespace::new(registry); - namespace.import_prelude(); - - let mut resolver = ImportResolver::new(env.heap, namespace); - resolver.visit_expr(expr); - diagnostics.append( - &mut resolver - .take_diagnostics() - .map_category(LoweringDiagnosticCategory::Resolver), - ); - - let mut mangler = NameMangler::new(env.heap); - mangler.visit_expr(expr); - - let mut extractor = TypeDefinitionExtractor::new(env, registry, module_name); - extractor.visit_expr(expr); - let (named_types, extractor_diagnostics) = extractor.finish(); - diagnostics - .append(&mut extractor_diagnostics.map_category(LoweringDiagnosticCategory::Extractor)); - - let mut renumberer = NodeRenumberer::new(); - renumberer.visit_expr(expr); - - let mut extractor = TypeExtractor::new(env, registry, &named_types); - extractor.visit_expr(expr); - diagnostics.append( - &mut extractor - .take_diagnostics() - .map_category(LoweringDiagnosticCategory::Extractor), - ); - let (anonymous_types, closure_signatures) = extractor.into_types(); - - let types = ExtractedTypes { - locals: named_types, - anonymous: anonymous_types, - signatures: closure_signatures, - }; - - let mut result = Status::success(types); - result.append_diagnostics(&mut diagnostics); - result -} diff --git a/libs/@local/hashql/ast/src/lowering/pre_expansion_name_resolver.rs b/libs/@local/hashql/ast/src/lowering/pre_expansion_name_resolver.rs deleted file mode 100644 index ce317f1879c..00000000000 --- a/libs/@local/hashql/ast/src/lowering/pre_expansion_name_resolver.rs +++ /dev/null @@ -1,437 +0,0 @@ -//! Name resolution for the HashQL Abstract Syntax Tree. -//! -//! This module provides functionality for resolving identifiers in the AST to their fully -//! qualified paths. It handles path resolution, scoping rules, and special form detection -//! for constructs like `let`, `type`, and `newtype`. -//! -//! # Overview -//! -//! Name resolution is a critical part of the HashQL compilation pipeline, converting: -//! -//! - Unqualified identifiers (`map`) to absolute paths (`::graph::body::map`) -//! - Operators (`+`) to their function equivalents (`::core::math::add`) -//! - Special forms to their kernel representations (`let` to `::kernel::special_form::let`) -//! -//! The resolver also manages scoping rules to ensure that bindings from `let`, `type`, and -//! `newtype` expressions only apply within their respective bodies. -//! -//! # Architecture -//! -//! The name resolver works as a visitor pattern implementation that traverses the AST and: -//! -//! 1. Maintains a mapping of identifiers to their absolute paths -//! 2. Applies transformations to convert relative paths to absolute ones -//! 3. Recognizes special forms and establishes proper scoping for bindings -//! 4. Preserves source location information during transformations -//! -//! # Examples -//! -//! Input AST: -//! ```json -//! ["let", "x", "10", ["+", "x", "5"]] -//! ``` -//! -//! After name resolution: -//! ```json -//! ["::kernel::special_form::let", "x", "10", ["::core::math::add", "10", "5"]] -//! ``` -//! -//! # Special Forms -//! -//! The resolver recognizes and properly processes several special forms: -//! -//! - **let**: Binds a value to a name within a scope -//! - `let/3`: `[let, name, value, body]` -//! - `let/4`: `[let, name, type, value, body]` -//! -//! - **type**: Defines a type alias within a scope -//! - `type/3`: `[type, name, underlying_type, body]` -//! -//! - **newtype**: Defines a new nominal type based on an existing type -//! - `newtype/3`: `[newtype, name, underlying_type, body]` -//! -//! # Path Resolution Behavior -//! -//! When processing paths, the resolver follows these rules: -//! -//! 1. Absolute paths (starting with `::`) remain unchanged -//! 2. Unrooted paths are checked against the current name mapping -//! 3. If the first segment matches an entry in the mapping, it's replaced with the absolute path -//! 4. Generic arguments are preserved during path resolution -//! 5. Source location information is maintained for error reporting -//! -//! # Selective Tree Resolution and Scope Separation -//! -//! The name resolver intentionally processes only specific parts of the AST: -//! -//! 1. We only resolve the function name. -//! 2. For `let` expressions, only the value of the binding is resolved. -//! -//! This selective approach is intentionally conservative and critical because: -//! -//! - It prepares the AST specifically for special form expansion, which only takes the function -//! name into account. -//! - It maintains a strict separation between value and type scopes, which don't share the same -//! identifier resolutions. -//! - The function called can only ever be a value, never a type, so we ensure consistent handling. -//! -//! By deliberately restricting name resolution to only the necessary parts of the tree, the -//! resolver prevents scope contamination and ensures correct expansion in subsequent compilation -//! phases. This conservative approach to resolution helps avoid unintended transformations that -//! could lead to subtle errors in later processing stages. -//! -//! # Scoping Rules -//! -//! The resolver enforces lexical scoping rules: -//! -//! 1. Bindings only apply within their defined scope (body of let/type/newtype expressions) -//! 2. Inner bindings shadow outer bindings with the same name -//! 3. Original bindings are restored when exiting a scope -//! 4. Built-in names can be shadowed by local bindings -use core::mem; - -use hashql_core::{ - collections::FastHashMap, - heap::{CollectIn as _, Heap}, - module::{ - ModuleRegistry, Reference, Universe, - item::{IntrinsicItem, IntrinsicValueItem, ItemKind}, - namespace::{ModuleNamespace, ResolutionMode, ResolveOptions}, - }, - span::SpanId, - symbol::{Ident, IdentKind, Symbol}, -}; - -use crate::{ - node::{ - expr::{CallExpr, ExprKind}, - id::NodeId, - path::{Path, PathSegment}, - }, - visit::{Visitor, walk_call_expr, walk_path}, -}; - -/// Resolves name aliases in the HashQL AST, converting identifiers to their absolute path -/// representation. -/// -/// The `NameResolver` performs several key functions during AST processing: -/// -/// 1. Path Resolution: Converts unrooted paths (like `map`) to their absolute forms (like -/// `::graph::body::map`) -/// 2. Special Form Handling: Recognizes and processes special forms like `let`, `type`, and -/// `newtype`, but does **not** transform them yet. -/// 3. Scope Management: Maintains proper lexical scoping of bindings within expressions -/// 4. Selective Value-Scope Resolution: Only resolves specific parts of the AST (binding values and -/// function identifiers) to maintain strict separation between value and type scopes -/// -/// # Example -/// -/// The name resolver converts expressions like: -/// ```json -/// ["let", "x", "10", ["+", "x", "5"]] -/// ``` -/// -/// Into their resolved forms: -/// ```json -/// [ -/// "::kernel::special_form::let", -/// "x", -/// "10", -/// ["::core::math::add", "10", "5"], -/// ] -/// ``` -/// -/// Note that the identifier `x` in the binding position (first argument) is preserved exactly as -/// written, while its use in the body expression is resolved according to the current scope. -pub struct PreExpansionNameResolver<'env, 'heap> { - alias: FastHashMap, Option>>, - - namespace: ModuleNamespace<'env, 'heap>, - namespace_cache: FastHashMap, Path<'heap>>, - - resolve: bool, - heap: &'heap Heap, -} - -impl<'env, 'heap> PreExpansionNameResolver<'env, 'heap> { - /// Creates a new `NameResolver` with an empty mapping. - pub fn new(registry: &'env ModuleRegistry<'heap>) -> Self { - let mut namespace = ModuleNamespace::new(registry); - namespace.import_prelude(); - - Self { - alias: FastHashMap::default(), - namespace, - namespace_cache: FastHashMap::default(), - resolve: false, - heap: registry.heap, - } - } - - fn walk_call( - &mut self, - expr: &mut CallExpr<'heap>, - to: Ident<'heap>, - mut from: Option>, - ) { - // We now need to call_expr, but it's important that we don't apply the mapping - // indiscriminately but instead we do so selectively on only on the last argument, as that - // is the body of the expression. - - let CallExpr { - id, - span, - // We don't need to visit the function, as we've already visited it - function: _, - arguments, - // We've checked beforehand that there are no labeled arguments, therefore it's - // pointless to visit them - labeled_arguments: _, - } = expr; - - self.visit_id(id); - self.visit_span(span); - - let len = arguments.len(); - - // While our call to `visit_argument` simply delegates to `visit_expr`, it's still important - // to call it, as to not break any contracts down the line. - for (index, argument) in arguments.iter_mut().enumerate() { - if index == 0 { - // The first argument is the identifier, which we shouldn't normalize - } else if index == len - 1 { - let old = if let Some(from) = from.take() { - self.alias.insert(to.value, Some(from)) - } else { - // Explicitly unset the alias - self.alias.insert(to.value, None) - }; - - self.visit_argument(argument); - - if let Some(old) = old { - self.alias.insert(to.value, old); - } else { - // The binding hasn't existed before, therefore restoration = deletion - self.alias.remove(&to.value); - } - } else { - self.visit_argument(argument); - } - } - } - - /// Looks up the absolute path for a given symbol name. - /// - /// It checks the local alias map first, then the namespace cache, and finally - /// the module namespace (for intrinsics) if necessary. Caches namespace lookups. - fn lookup(&mut self, name: Symbol<'heap>) -> Option> { - if let Some(replacement) = self.alias.get(&name) { - return replacement.clone(); - } - - // Check first if the cache has a version that's already been resolved - if let Some(path) = self.namespace_cache.get(&name) { - return Some(path.clone()); - } - - // This is very conservative, in *theory* we should take a look at the whole path and use - // that as import, but as we're only interested in special-forms, which are only imported as - // name, we can safely just use the name. - let reference = self - .namespace - .resolve( - [name], - ResolveOptions { - mode: ResolutionMode::Relative, - universe: Universe::Value, - }, - ) - .ok()?; - - let Reference::Item(import) = reference else { - return None; - }; - - // We're only interested in intrinsics - let ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { - name: path, - r#type: _, - })) = import.kind - else { - return None; - }; - - // The name is a fully qualified path, that we need to convert into a path - let (rooted, path) = path - .strip_prefix("::") - .map_or((false, path), |path| (true, path)); - - let segments = path - .split("::") - .map(|name| PathSegment { - id: NodeId::PLACEHOLDER, - span: SpanId::SYNTHETIC, - name: Ident { - span: SpanId::SYNTHETIC, - value: self.heap.intern_symbol(name), - kind: IdentKind::Lexical, - }, - arguments: Vec::new_in(self.heap), - }) - .collect_in(self.heap); - - let path = Path { - id: NodeId::PLACEHOLDER, - span: SpanId::SYNTHETIC, - rooted, - segments, - }; - - self.namespace_cache.insert(name, path.clone()); - - Some(path) - } -} - -impl<'heap> Visitor<'heap> for PreExpansionNameResolver<'_, 'heap> { - fn visit_path(&mut self, path: &mut Path<'heap>) { - if !self.resolve { - walk_path(self, path); - return; - } - - if path.rooted { - walk_path(self, path); - return; - } - - // Check if the first segment exists, and if said segment exists in our mapping - let Some(segment) = path.segments.first_mut() else { - walk_path(self, path); - return; - }; - - // The mapping can either exist in the registry, or our alias mapping - - let Some(replacement) = self.lookup(segment.name.value) else { - walk_path(self, path); - return; - }; - - let mut arguments = Some(mem::replace(&mut segment.arguments, Vec::new_in(self.heap))); - - let span = segment.span; - - path.rooted = replacement.rooted; - - let replacement_len = replacement.segments.len(); - - // Replace the segment with the aliased value - path.segments.splice( - 0..1, - replacement - .segments - .into_iter() - .enumerate() - .map(|(index, mut segment)| { - // Make sure that we inherit the span from the original segment - segment.span = span; - - // Take the arguments if we're at the last segment, as converting from - // `a` to `math::add` wouldn't be a valid transformation. - if index == replacement_len - 1 { - segment.arguments = arguments.take().unwrap_or_else(|| unreachable!()); - } - - segment - }), - ); - - walk_path(self, path); - } - - fn visit_call_expr(&mut self, expr: &mut CallExpr<'heap>) { - // Look for expressions that is pre-expansion and *looks* like a let expressions - - // Special forms don't support labeled arguments - if !expr.labeled_arguments.is_empty() { - walk_call_expr(self, expr); - return; - } - - // Check if the argument is a path that can be an ident - let ExprKind::Path(function) = &mut expr.function.kind else { - walk_call_expr(self, expr); - return; - }; - - // First resolve the path - // In theory as we're accessing the path here, we'd need to call `visit_id` and `visit_span` - // as well, but as we don't actually implement these methods, and they're no-op we're free - // to omit those calls. - self.resolve = true; - self.visit_path(function); - self.resolve = false; - - // `let` supports two forms: `let/3` and `let/4` (w/ or w/o type assertion) - if expr.arguments.len() != 3 && expr.arguments.len() != 4 { - walk_call_expr(self, expr); - return; - } - - // Check if said path is equivalent to the let special form - if !function.matches_absolute_path(["kernel", "special_form", "let"]) { - walk_call_expr(self, expr); - return; - } - - let arguments_length = expr.arguments.len(); - - // we know this is a let expression, now we just need to make sure that both the first and - // second-to-last argument are identifiers - // let to: = from in - let [to, from] = expr - .arguments - .get_disjoint_mut([0, arguments_length - 2]) - .expect("length has been verified beforehand"); - - let ExprKind::Path(to) = &mut to.value.kind else { - walk_call_expr(self, expr); - return; - }; - - // We do **not** resolve the `to` path, as it is supposed to be an identifier - let Some(to) = to.as_ident().copied() else { - walk_call_expr(self, expr); - return; - }; - - let ExprKind::Path(from) = &mut from.value.kind else { - // While it isn't a path and therefore not an alias, this is still a valid assignment, - // therefore we need to actually *remove* the mapping for the duration of the call. - - self.walk_call(expr, to, None); - return; - }; - - // Check that the path itself is not generic, if it is, there is no safe way to create an - // alias - if from - .segments - .iter() - .any(|segment| !segment.arguments.is_empty()) - { - walk_call_expr(self, expr); - return; - } - - // We have a new mapping from path to type - self.resolve = true; - self.visit_path(from); - self.resolve = false; - let from = Some(from.clone()); - - self.walk_call(expr, to, from); - } -} diff --git a/libs/@local/hashql/ast/src/lowering/special_form_expander/error.rs b/libs/@local/hashql/ast/src/lowering/special_form_expander/error.rs deleted file mode 100644 index 96bd274ca3d..00000000000 --- a/libs/@local/hashql/ast/src/lowering/special_form_expander/error.rs +++ /dev/null @@ -1,1125 +0,0 @@ -use alloc::borrow::Cow; -use core::fmt::{self, Display}; - -use hashql_core::{algorithms::did_you_mean, span::SpanId}; -use hashql_diagnostics::{ - Diagnostic, DiagnosticIssues, Label, - category::{DiagnosticCategory, TerminalDiagnosticCategory}, - diagnostic::Message, - severity::Severity, -}; - -use super::SpecialFormKind; -use crate::node::{ - expr::call::{Argument, LabeledArgument}, - path::{Path, PathSegmentArgument}, -}; - -pub(crate) type SpecialFormExpanderDiagnostic = - Diagnostic; - -pub(crate) type SpecialFormExpanderDiagnosticIssues = - DiagnosticIssues; - -const UNKNOWN_SPECIAL_FORM: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "unknown-special-form", - name: "Unknown special form", -}; - -const SPECIAL_FORM_ARGUMENT_LENGTH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "special-form-argument-length", - name: "Incorrect number of arguments for special form", -}; - -const LABELED_ARGUMENTS_NOT_SUPPORTED: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "labeled-arguments-not-supported", - name: "Labeled arguments not supported in special forms", -}; - -const INVALID_TYPE_EXPRESSION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-type-expression", - name: "Invalid type expression", -}; - -const INVALID_TYPE_CALL: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-type-call", - name: "Invalid function call in type expression", -}; - -const UNSUPPORTED_TYPE_CONSTRUCTOR: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "unsupported-type-constructor", - name: "Unsupported type constructor", -}; - -const INVALID_BINDING_NAME: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-binding-name", - name: "Invalid binding name", -}; - -const QUALIFIED_BINDING_NAME: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "qualified-binding-name", - name: "Qualified path used as binding name", -}; - -const TYPE_WITH_EXISTING_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "type-with-existing-annotation", - name: "Type expression with redundant type annotation", -}; - -const INVALID_USE_IMPORT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-use-import", - name: "Invalid use import expression", -}; - -const USE_PATH_WITH_GENERICS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "use-path-with-generics", - name: "Use path with generic arguments", -}; - -const FN_GENERICS_WITH_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "fn-generics-with-type-annotation", - name: "Function generics with type annotation", -}; - -const INVALID_FN_GENERICS_EXPRESSION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-fn-generics-expression", - name: "Invalid expression in function generics", -}; - -const INVALID_FN_PARAMS_EXPRESSION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-fn-params-expression", - name: "Invalid expression in function parameters", -}; - -const FN_PARAMS_WITH_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "fn-params-with-type-annotation", - name: "Function parameters with type annotation", -}; - -const INVALID_FN_GENERIC_PARAM: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-fn-generic-param", - name: "Invalid generic parameter in function declaration", -}; - -const INVALID_GENERIC_ARGUMENT_PATH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-generic-argument-path", - name: "Invalid path in generic argument", -}; - -const INVALID_GENERIC_ARGUMENT_TYPE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-generic-argument-type", - name: "Invalid type in generic argument", -}; - -const DUPLICATE_GENERIC_CONSTRAINT: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "duplicate-generic-constraint", - name: "Duplicate generic parameter constraint", -}; - -const DUPLICATE_CLOSURE_PARAMETER: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "duplicate-closure-parameter", - name: "Duplicate closure parameter", -}; - -const FIELD_LITERAL_TYPE_ANNOTATION: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "field-literal-type-annotation", - name: "Field literal with type annotation", -}; - -const INVALID_FIELD_LITERAL_TYPE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "invalid-field-literal-type", - name: "Invalid field literal type", -}; - -const FIELD_INDEX_OUT_OF_BOUNDS: TerminalDiagnosticCategory = TerminalDiagnosticCategory { - id: "field-index-out-of-bounds", - name: "Field index out of bounds", -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum SpecialFormExpanderDiagnosticCategory { - UnknownSpecialForm, - SpecialFormArgumentLength, - LabeledArgumentsNotSupported, - InvalidTypeExpression, - InvalidTypeCall, - UnsupportedTypeConstructor, - InvalidBindingName, - QualifiedBindingName, - TypeWithExistingAnnotation, - InvalidUseImport, - UsePathWithGenerics, - FnGenericsWithTypeAnnotation, - InvalidFnGenericsExpression, - InvalidFnParamsExpression, - FnParamsWithTypeAnnotation, - InvalidFnGenericParam, - InvalidGenericArgumentPath, - InvalidGenericArgumentType, - DuplicateGenericConstraint, - DuplicateClosureParameter, - FieldLiteralTypeAnnotation, - InvalidFieldLiteralType, - FieldIndexOutOfBounds, -} - -impl DiagnosticCategory for SpecialFormExpanderDiagnosticCategory { - fn id(&self) -> Cow<'_, str> { - Cow::Borrowed("special-form-expander") - } - - fn name(&self) -> Cow<'_, str> { - Cow::Borrowed("Special Form Expander") - } - - fn subcategory(&self) -> Option<&dyn DiagnosticCategory> { - match self { - Self::UnknownSpecialForm => Some(&UNKNOWN_SPECIAL_FORM), - Self::SpecialFormArgumentLength => Some(&SPECIAL_FORM_ARGUMENT_LENGTH), - Self::LabeledArgumentsNotSupported => Some(&LABELED_ARGUMENTS_NOT_SUPPORTED), - Self::InvalidTypeExpression => Some(&INVALID_TYPE_EXPRESSION), - Self::InvalidTypeCall => Some(&INVALID_TYPE_CALL), - Self::UnsupportedTypeConstructor => Some(&UNSUPPORTED_TYPE_CONSTRUCTOR), - Self::InvalidBindingName => Some(&INVALID_BINDING_NAME), - Self::QualifiedBindingName => Some(&QUALIFIED_BINDING_NAME), - Self::TypeWithExistingAnnotation => Some(&TYPE_WITH_EXISTING_ANNOTATION), - Self::InvalidUseImport => Some(&INVALID_USE_IMPORT), - Self::UsePathWithGenerics => Some(&USE_PATH_WITH_GENERICS), - Self::FnGenericsWithTypeAnnotation => Some(&FN_GENERICS_WITH_TYPE_ANNOTATION), - Self::InvalidFnGenericsExpression => Some(&INVALID_FN_GENERICS_EXPRESSION), - Self::InvalidFnParamsExpression => Some(&INVALID_FN_PARAMS_EXPRESSION), - Self::FnParamsWithTypeAnnotation => Some(&FN_PARAMS_WITH_TYPE_ANNOTATION), - Self::InvalidFnGenericParam => Some(&INVALID_FN_GENERIC_PARAM), - Self::InvalidGenericArgumentPath => Some(&INVALID_GENERIC_ARGUMENT_PATH), - Self::InvalidGenericArgumentType => Some(&INVALID_GENERIC_ARGUMENT_TYPE), - Self::DuplicateGenericConstraint => Some(&DUPLICATE_GENERIC_CONSTRAINT), - Self::DuplicateClosureParameter => Some(&DUPLICATE_CLOSURE_PARAMETER), - Self::FieldLiteralTypeAnnotation => Some(&FIELD_LITERAL_TYPE_ANNOTATION), - Self::InvalidFieldLiteralType => Some(&INVALID_FIELD_LITERAL_TYPE), - Self::FieldIndexOutOfBounds => Some(&FIELD_INDEX_OUT_OF_BOUNDS), - } - } -} - -pub(crate) fn unknown_special_form_length( - span: SpanId, - path: &Path<'_>, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::UnknownSpecialForm, - Severity::Error, - ) - .primary(Label::new(span, "Fix this path to have exactly 3 segments")); - - if path.segments.len() > 3 { - // Point to the problematic segment(s) - for segment in path.segments.iter().skip(3) { - diagnostic - .labels - .push(Label::new(segment.span, "Remove this extra segment")); - } - } - - diagnostic.add_message(Message::help( - "Special form paths must follow the pattern '::kernel::special_form::' with exactly \ - 3 segments", - )); - - diagnostic.add_message(Message::note(format!( - "Found path with {} segments, but special form paths must have exactly 3 segments", - path.segments.len() - ))); - - diagnostic -} - -pub(crate) fn unknown_special_form_name( - span: SpanId, - path: &Path<'_>, -) -> SpecialFormExpanderDiagnostic { - let function_name = path.segments[2].name.value; - let function_span = path.segments[2].name.span; - - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::UnknownSpecialForm, - Severity::Error, - ) - .primary(Label::new( - function_span, - format!("Replace '{function_name}' with a valid special form name"), - )); - - diagnostic - .labels - .push(Label::new(span, "This special form path is invalid")); - - let mut closest_match = did_you_mean( - function_name, - enum_iterator::all::() - .map(|kind| path.segments.allocator().intern_symbol(kind.as_str())), - Some(1), - None, - ); - - let help = closest_match.pop().map_or( - Cow::Borrowed("Special forms must use one of the predefined names shown in the note below"), - |kind| Cow::Owned(format!("Did you mean to use '{kind}' instead?")), - ); - - diagnostic.add_message(Message::help(help)); - - let names = enum_iterator::all::() - .map(SpecialFormKind::as_str) - .collect::>() - .join(", "); - - diagnostic.add_message(Message::note(format!( - "Available special forms include: {names}" - ))); - - diagnostic -} - -pub(crate) fn unknown_special_form_generics( - generics: &[&PathSegmentArgument], -) -> SpecialFormExpanderDiagnostic { - let (first, rest) = generics - .split_first() - .expect("should have at least one generic argument"); - - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::UnknownSpecialForm, - Severity::Error, - ) - .primary(Label::new(first.span(), "Remove these generic arguments")); - - for argument in rest { - diagnostic - .labels - .push(Label::new(argument.span(), "... and these too")); - } - - diagnostic.add_message(Message::help( - "Special form paths must not include generic arguments. Remove the angle brackets and \ - their contents.", - )); - - diagnostic.add_message(Message::note( - "Special forms are built-in language constructs that don't support generics in their path \ - reference.", - )); - - diagnostic -} - -pub(super) fn invalid_argument_length( - span: SpanId, - kind: SpecialFormKind, - arguments: &[Argument], - expected: &[usize], -) -> SpecialFormExpanderDiagnostic { - let diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::SpecialFormArgumentLength, - Severity::Error, - ); - - let actual = arguments.len(); - - let canonical: Vec<_> = expected - .iter() - .map(|length| format!("{kind}/{length}")) - .collect(); - - let max_expected = expected.iter().max().copied().unwrap_or(0); - - let mut diagnostic = if actual > max_expected { - let excess = &arguments[max_expected..]; - - let (first, rest) = excess.split_first().unwrap_or_else(|| unreachable!()); - - let mut diagnostic = diagnostic.primary(Label::new(first.span, "Remove this argument")); - - for argument in rest { - diagnostic - .labels - .push(Label::new(argument.span, "... and this argument")); - } - - diagnostic.labels.push(Label::new( - span, - format!("In this `{kind}` special form call"), - )); - - diagnostic - } else { - diagnostic.primary(Label::new(span, "Add missing arguments")) - }; - - // Specific help text with code examples - let help_text = match kind { - SpecialFormKind::If => { - "Use either:\n- if/2: (if condition then-expr)\n- if/3: (if condition then-expr \ - else-expr)" - } - SpecialFormKind::As => "The as/2 form should look like: (as value type-expr)", - SpecialFormKind::Let => { - "Use either:\n- let/3: (let name value body)\n- let/4: (let name type value body)" - } - SpecialFormKind::Type => "The type/3 form should look like: (type name type-expr body)", - SpecialFormKind::Newtype => { - "The newtype/3 form should look like: (newtype name type-expr body)" - } - SpecialFormKind::Use => "The use/3 form should look like: (use module imports body)", - SpecialFormKind::Fn => { - "The fn/4 form should look like: (fn generics arguments return-type body)" - } - SpecialFormKind::Input => { - "Use either:\n- input/2: (input name type)\n- input/3: (input name type default)" - } - SpecialFormKind::Access => "The access/2 form should look like: (. object field)", - SpecialFormKind::Index => "The index/2 form should look like: ([] object index)", - }; - - diagnostic.add_message(Message::help(help_text)); - - diagnostic.add_message(Message::note(format!( - "The {kind} function has {} variant{}: {}", - expected.len(), - if expected.len() == 1 { "" } else { "s" }, - canonical.join(", ") - ))); - - diagnostic -} - -pub(crate) fn labeled_arguments_not_supported( - span: SpanId, - arguments: &[LabeledArgument], -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::LabeledArgumentsNotSupported, - Severity::Error, - ) - .primary(Label::new(span, "In this special form call")); - - let (first, rest) = arguments.split_first().unwrap_or_else(|| unreachable!()); - - diagnostic - .labels - .push(Label::new(first.span, "Remove this labeled argument")); - - for argument in rest { - diagnostic - .labels - .push(Label::new(argument.span, "... and this labeled argument")); - } - - diagnostic.add_message(Message::help( - "Special forms only accept positional arguments. Convert all labeled arguments to \ - positional arguments in the correct order.", - )); - - diagnostic.add_message(Message::note( - "Unlike regular functions, special forms have fixed parameter positions and cannot use \ - labeled arguments.", - )); - - diagnostic -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) enum InvalidTypeExpressionKind { - Dict, - List, - Literal, - Let, - Type, - NewType, - Use, - Input, - Closure, - If, - Field, - Index, - As, - Dummy, -} - -impl InvalidTypeExpressionKind { - const fn as_str(self) -> &'static str { - match self { - Self::Dict => "dictionary", - Self::List => "list", - Self::Literal => "literal", - Self::Let => "let binding", - Self::Type => "type definition", - Self::NewType => "newtype definition", - Self::Use => "use", - Self::Input => "input", - Self::Closure => "function", - Self::If => "if", - Self::Field => "field access", - Self::Index => "index", - Self::As => "as", - Self::Dummy => "dummy", - } - } -} - -impl Display for InvalidTypeExpressionKind { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.write_str(self.as_str()) - } -} - -const TYPE_EXPRESSION_NOTE: &str = "Valid type expressions include: -- Type names: String, Int, Float -- Struct types: {name: String, age: Int} -- Tuple types: (String, Int, Boolean) -- Unions: (| String Int) -- Intersections: (& String Int) -- Generic types: Array, Option"; - -pub(crate) fn invalid_type_expression( - span: SpanId, - kind: InvalidTypeExpressionKind, -) -> SpecialFormExpanderDiagnostic { - let diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidTypeExpression, - Severity::Error, - ); - - let message = match kind { - InvalidTypeExpressionKind::Literal => { - Cow::Borrowed("Replace this literal with a type name") - } - InvalidTypeExpressionKind::If => { - Cow::Borrowed("Replace this conditional with a concrete type") - } - InvalidTypeExpressionKind::Dict - | InvalidTypeExpressionKind::List - | InvalidTypeExpressionKind::Let - | InvalidTypeExpressionKind::Type - | InvalidTypeExpressionKind::NewType - | InvalidTypeExpressionKind::Use - | InvalidTypeExpressionKind::Input - | InvalidTypeExpressionKind::Closure - | InvalidTypeExpressionKind::Field - | InvalidTypeExpressionKind::Index - | InvalidTypeExpressionKind::As - | InvalidTypeExpressionKind::Dummy => { - Cow::Owned(format!("Replace this {kind} with a proper type expression")) - } - }; - - let mut diagnostic = diagnostic.primary(Label::new(span, message)); - - let help_text = match kind { - InvalidTypeExpressionKind::Dict => { - "Dictionaries do not constitute a valid type expression, did you mean to instantiate a \ - struct type or refer to the `Dict` type?" - } - InvalidTypeExpressionKind::List => { - "Arrays do not constitute a valid type expression, did you mean to instantiate a tuple \ - type or refer to the `Array` type?" - } - InvalidTypeExpressionKind::If => { - "HashQL does not support conditional types. Use a concrete type like Int or String." - } - InvalidTypeExpressionKind::Literal - | InvalidTypeExpressionKind::Let - | InvalidTypeExpressionKind::Type - | InvalidTypeExpressionKind::NewType - | InvalidTypeExpressionKind::Use - | InvalidTypeExpressionKind::Input - | InvalidTypeExpressionKind::Closure - | InvalidTypeExpressionKind::Field - | InvalidTypeExpressionKind::Index - | InvalidTypeExpressionKind::As - | InvalidTypeExpressionKind::Dummy => { - "Replace this expression with a valid type reference, struct type, or tuple type" - } - }; - - diagnostic.add_message(Message::help(help_text)); - - diagnostic.add_message(Message::note(TYPE_EXPRESSION_NOTE)); - - diagnostic -} - -pub(crate) fn invalid_type_call_function(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidTypeExpression, - Severity::Error, - ) - .primary(Label::new( - span, - "Function call with non-path callee cannot be used as a type", - )); - - diagnostic.add_message(Message::help( - "Only specific type constructors like intersection (&) and union (|) operators can be \ - used in type expressions.", - )); - - diagnostic.add_message(Message::note(TYPE_EXPRESSION_NOTE)); - - diagnostic -} - -pub(crate) fn unsupported_type_constructor_function(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidTypeExpression, - Severity::Error, - ) - .primary(Label::new( - span, - "This function cannot be used as a type constructor", - )); - - diagnostic.add_message(Message::help( - "Only specific type constructors like intersection (&) and union (|) operators can be \ - used in type expressions.", - )); - - diagnostic.add_message(Message::note( - "Currently supported type operations are:\n- Intersection: math::bit_and (written as & in \ - source)\n- Union: math::bit_or (written as | in source)", - )); - - diagnostic -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, derive_more::Display)] -pub(crate) enum BindingMode { - #[display("use")] - Use, - #[display("let")] - Let, - #[display("type")] - Type, - #[display("newtype")] - Newtype, - #[display("input")] - Input, - #[display("`.`")] - Access, -} - -pub(crate) fn invalid_binding_name_not_path( - span: SpanId, - mode: BindingMode, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidBindingName, - Severity::Error, - ) - .primary(Label::new( - span, - "Replace this expression with a simple identifier", - )); - - diagnostic.add_message(Message::help(format!( - "The {mode} binding name must be a simple identifier. Complex expressions are not allowed \ - in binding positions." - ))); - - let note = match mode { - BindingMode::Use => { - "Valid examples of use bindings:\n- (use module_name * body)\n- (use ::math (sin, cos) \ - ...)\n- (use ::string (trim: string_trim) ...)" - } - BindingMode::Let => { - "Valid examples of let bindings:\n- (let x value body)\n- (let counter 0 ...)\n- (let \ - user_name input ...)" - } - BindingMode::Type => { - "Valid examples of type bindings:\n- (type Person (name: String, age: Int) body)\n- \ - (type Output (| Integer Natural) ...)\n- (type UserId String ...)" - } - BindingMode::Newtype => { - "Valid examples of newtype bindings:\n- (newtype UserId String body)\n- (newtype Email \ - String ...)\n- (newtype Percentage Number ...)" - } - BindingMode::Input => { - "Valid examples of input bindings:\n- (input name String)\n- (input age Int \ - default_age)\n- (input options (enabled: Boolean))" - } - BindingMode::Access => { - "Valid examples of access bindings:\n- (. user name)\n- (. person age)\n- (. options \ - enabled)" - } - }; - - diagnostic.add_message(Message::note(note)); - - diagnostic -} - -pub(crate) fn invalid_let_name_qualified_path( - span: SpanId, - mode: BindingMode, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::QualifiedBindingName, - Severity::Error, - ) - .primary(Label::new(span, "Replace this with a simple identifier")); - - diagnostic.add_message(Message::help(format!( - "{mode} binding names must be simple identifiers without any path qualification. \ - Qualified paths cannot be used as binding names." - ))); - - diagnostic.add_message(Message::note( - "Valid identifiers are simple names like 'x', 'counter', '+', or 'user_name' without any \ - namespace qualification, generic parameters, or path separators.", - )); - - diagnostic -} - -pub(crate) fn invalid_type_name_qualified_path( - span: SpanId, - mode: BindingMode, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::QualifiedBindingName, - Severity::Error, - ) - .primary(Label::new( - span, - "Replace this qualified path with a simple identifier", - )); - - diagnostic.add_message(Message::help(format!( - "The {mode} binding requires a simple type name (like 'String' or 'MyType'), not a \ - qualified path (like 'std::string::String'). Remove the path segments." - ))); - - diagnostic.add_message(Message::note( - "Valid type names are simple identifiers, optionally followed by generic arguments (e.g., \ - 'Identifier' or 'Container'). They cannot contain '::' path separators in this \ - context.", - )); - - diagnostic -} - -pub(crate) fn type_with_existing_annotation(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::TypeWithExistingAnnotation, - Severity::Error, - ) - .primary(Label::new(span, "Remove this type annotation")); - - diagnostic.add_message(Message::help( - "Type expressions used in special forms cannot have their own type annotations. The \ - expression itself defines a type and cannot be annotated with another type.", - )); - - diagnostic.add_message(Message::note( - "When constructing type expressions for special forms like 'type', 'newtype', or 'as', \ - the expression itself represents a type definition and cannot have a separate type \ - annotation.", - )); - - diagnostic -} - -pub(crate) fn invalid_use_import(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidUseImport, - Severity::Error, - ) - .primary(Label::new(span, "Replace with a valid import expression")); - - diagnostic.add_message(Message::help( - "Use imports must be either a glob (*), a tuple of identifiers, or a struct of bindings. \ - Other expression types are not valid in this context.", - )); - - diagnostic.add_message(Message::note( - "Valid import expressions include:\n- Glob: *\n- Tuple of identifiers: (name1, name2)\n- \ - Struct with aliases: (name1: alias1, name2: alias2) or (name1: _, name2: _)", - )); - - diagnostic -} - -pub(crate) fn use_imports_with_type_annotation(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidUseImport, - Severity::Error, - ) - .primary(Label::new(span, "Remove this type annotation")); - - diagnostic.add_message(Message::help( - "Use import expressions cannot have type annotations. Import expressions define which \ - symbols to import, and do not have a meaningful type in this context.", - )); - - diagnostic.add_message(Message::note( - "Import expressions in the 'use' special form can only be a glob (*), a tuple of \ - identifiers, or a struct of bindings, none of which should have type annotations.", - )); - - diagnostic -} - -pub(crate) fn invalid_path_in_use_binding(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidUseImport, - Severity::Error, - ) - .primary(Label::new(span, "Use a simple identifier here")); - - diagnostic.add_message(Message::help( - "Use binding names must be simple identifiers. Qualified paths or complex expressions \ - cannot be used in this context.", - )); - - diagnostic.add_message(Message::note( - "In tuple imports, each element must be a simple identifier. For example: (name1, name2) \ - is valid, but (path::to::name,) is not.", - )); - - diagnostic -} - -pub(crate) fn use_path_with_generics( - span: SpanId, - path: &Path<'_>, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::UsePathWithGenerics, - Severity::Error, - ) - .primary(Label::new(span, "Remove these generic arguments")); - - // Add labels for each generic argument segment in the path - for segment in &path.segments { - if !segment.arguments.is_empty() { - diagnostic.labels.push(Label::new( - segment.span, - "Generic arguments are not allowed here", - )); - } - } - - diagnostic.add_message(Message::help( - "The 'use' special form does not support generic arguments in import paths. Remove all \ - generic arguments from the path.", - )); - - diagnostic.add_message(Message::note( - "Use statements in HashQL can only import modules or specific symbols, but cannot specify \ - generic parameters during import.", - )); - - diagnostic -} - -pub(crate) fn fn_generics_with_type_annotation(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::FnGenericsWithTypeAnnotation, - Severity::Error, - ) - .primary(Label::new(span, "Remove this type annotation")); - - diagnostic.add_message(Message::help( - "Function generics declarations cannot have type annotations. Generic parameter lists \ - define type parameters for the function, and do not have a meaningful type themselves.", - )); - - diagnostic.add_message(Message::note( - "In the 'fn' special form, the generics argument should be either a tuple of identifiers \ - such as (T, U) or a struct of bounded type parameters such as (T: SomeBound, U: \ - OtherBound, V: _), where an underscore indicates no bound.", - )); - - diagnostic -} - -pub(crate) fn invalid_fn_generics_expression(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidFnGenericsExpression, - Severity::Error, - ) - .primary(Label::new(span, "Use a valid generics expression")); - - diagnostic.add_message(Message::help( - "Function generics must be specified as either a tuple of identifiers or a struct of \ - bounded type parameters. Other expression types are not valid in this context.", - )); - - diagnostic.add_message(Message::note( - "Valid generics expressions include:\n- Empty: ()\n- Tuple of identifiers: (T, U, V)\n- \ - Struct with bounds: (T: SomeBound, U: OtherBound) or (T: _, U: _) for unbounded types", - )); - - diagnostic -} - -pub(crate) fn invalid_fn_generic_param(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidFnGenericParam, - Severity::Error, - ) - .primary(Label::new(span, "Use a simple identifier here")); - - diagnostic.add_message(Message::help( - "Generic type parameters must be simple identifiers. Qualified paths or complex \ - expressions cannot be used in this context.", - )); - - diagnostic.add_message(Message::note( - "In function generic parameter lists, each element must be a simple identifier. For \ - example: (T, U, V) is valid, but (some::path,) is not.", - )); - - diagnostic -} - -pub(crate) fn fn_params_with_type_annotation(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::FnParamsWithTypeAnnotation, - Severity::Error, - ) - .primary(Label::new(span, "Remove this type annotation")); - - diagnostic.add_message(Message::help( - "Function parameter declarations cannot have type annotations at the struct level. The \ - struct itself represents the parameter list, and each field represents a parameter with \ - its type.", - )); - - diagnostic.add_message(Message::note( - "In the 'fn' special form, parameter lists should be structured as (param1: Type1, \ - param2: Type2), where the struct itself does not have a type annotation.", - )); - - diagnostic -} - -pub(crate) fn invalid_fn_params_expression(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidFnParamsExpression, - Severity::Error, - ) - .primary(Label::new(span, "Use a struct expression for parameters")); - - diagnostic.add_message(Message::help( - "Function parameters must be specified as a struct where field names are parameter names \ - and field values are parameter types. Other expression types are not valid in this \ - context.", - )); - - diagnostic.add_message(Message::note( - "Valid parameter expression is a struct in the form: (param1: Type1, param2: Type2, ...)", - )); - - diagnostic -} - -pub(crate) fn invalid_generic_argument_path(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidGenericArgumentPath, - Severity::Error, - ) - .primary(Label::new(span, "Replace with a simple identifier")); - - diagnostic.add_message(Message::help( - "Generic arguments must be simple identifiers. Qualified paths cannot be used as generic \ - arguments in this context.", - )); - - diagnostic.add_message(Message::note( - "In generic parameter constraints, arguments should be simple identifiers like 'T', 'U', \ - or 'Element' without namespace qualification or path separators.", - )); - - diagnostic -} - -pub(crate) fn invalid_generic_argument_type(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidGenericArgumentType, - Severity::Error, - ) - .primary(Label::new(span, "Use a simple type identifier here")); - - diagnostic.add_message(Message::help( - "Generic argument types must be simple path identifiers. Complex types like structs, \ - tuples, or function types cannot be used as generic argument types in this context.", - )); - - diagnostic.add_message(Message::note( - "Valid generic argument types are simple identifiers that refer to type names, such as \ - 'String', 'Number', or type parameters like 'T'.", - )); - - diagnostic -} - -pub(crate) fn duplicate_generic_constraint( - span: SpanId, - param: &str, - original_span: SpanId, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::DuplicateGenericConstraint, - Severity::Error, - ) - .primary(Label::new( - span, - format!("Remove this duplicate declaration of '{param}'"), - )); - - diagnostic.labels.push(Label::new( - original_span, - format!("'{param}' was previously declared here"), - )); - - diagnostic.add_message(Message::help( - "Each generic parameter can only be declared once in a function or type definition. \ - Remove the duplicate declaration or use a different name.", - )); - - diagnostic.add_message(Message::note( - "Generic parameter names must be unique within a single generic parameter list. For \ - example, in foo, 'T' and 'U' are unique parameters.", - )); - - diagnostic -} - -pub(crate) fn duplicate_closure_parameter( - span: SpanId, - param: &str, - original_span: SpanId, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::DuplicateClosureParameter, - Severity::Error, - ) - .primary(Label::new( - span, - format!("Remove this duplicate parameter '{param}'"), - )); - - diagnostic.labels.push(Label::new( - original_span, - format!("'{param}' was previously declared here"), - )); - - diagnostic.add_message(Message::help( - "Each function parameter must have a unique name. Rename this parameter or remove the \ - duplicate declaration.", - )); - - diagnostic.add_message(Message::note( - "Function parameters must have unique names within the same parameter list. For example, \ - in fn(x: Int, y: String): ReturnType body), 'x' and 'y' are unique parameters.", - )); - - diagnostic -} - -pub(crate) fn duplicate_closure_generic( - span: SpanId, - param_name: &str, - original_span: SpanId, -) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::DuplicateGenericConstraint, - Severity::Error, - ) - .primary(Label::new( - span, - format!("Remove this duplicate generic parameter '{param_name}'"), - )); - - diagnostic.labels.push(Label::new( - original_span, - format!("'{param_name}' was previously declared here"), - )); - - diagnostic.add_message(Message::help( - "Each generic parameter can only be declared once in a function definition. Remove the \ - duplicate declaration or use a different name.", - )); - - diagnostic.add_message(Message::note( - "Generic parameter names must be unique within a function's generic parameter list. For \ - example, in fn(param: T): U -> body), 'T' and 'U' are unique parameters.", - )); - - diagnostic -} - -pub(crate) fn field_literal_type_annotation(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::FieldLiteralTypeAnnotation, - Severity::Error, - ) - .primary(Label::new(span, "Remove this type annotation")); - - diagnostic.add_message(Message::help( - "Field access using numeric literals cannot have type annotations. The literal represents \ - the field index directly and doesn't need a separate type.", - )); - - diagnostic.add_message(Message::note( - "When using numeric literals for field access (e.g., in tuple destructuring), the number \ - itself indicates the position and cannot be annotated with a type.", - )); - - diagnostic -} - -pub(crate) fn invalid_field_literal_type(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::InvalidFieldLiteralType, - Severity::Error, - ) - .primary(Label::new(span, "Use an integer literal here")); - - diagnostic.add_message(Message::help( - "Field access using literals requires an integer literal that specifies the field index. \ - Other literal types like strings, booleans, or numbers cannot be used for field indexing.", - )); - - diagnostic.add_message(Message::note( - "Valid field access examples:\n- (access tuple 0) - accesses first field\n- (access \ - record 2) - accesses third field\n- (access data my_field) - accesses named field", - )); - - diagnostic -} - -pub(crate) fn field_index_out_of_bounds(span: SpanId) -> SpecialFormExpanderDiagnostic { - let mut diagnostic = Diagnostic::new( - SpecialFormExpanderDiagnosticCategory::FieldIndexOutOfBounds, - Severity::Error, - ) - .primary(Label::new( - span, - "Use a valid field index within usize bounds", - )); - - diagnostic.add_message(Message::help( - "Field indices must be non-negative integers that fit within the bounds of usize. Very \ - large numbers or negative numbers cannot be used as field indices.", - )); - - diagnostic.add_message(Message::note(format!( - "Field indexing in HashQL uses zero-based indexing where the first field is at index 0, \ - the second at index 1, and so on. The maximum valid index depends on the system's \ - architecture, which is {}-bit.", - usize::BITS - ))); - - diagnostic -} diff --git a/libs/@local/hashql/ast/src/lowering/special_form_expander/mod.rs b/libs/@local/hashql/ast/src/lowering/special_form_expander/mod.rs deleted file mode 100644 index 155d041aee9..00000000000 --- a/libs/@local/hashql/ast/src/lowering/special_form_expander/mod.rs +++ /dev/null @@ -1,1451 +0,0 @@ -pub mod error; - -use core::{ - fmt::{self, Display}, - mem, -}; - -use hashql_core::{ - collections::{FastHashMap, fast_hash_map_with_capacity}, - heap::{self, Heap}, - span::SpanId, - symbol::{Ident, IdentKind}, - value::Primitive, -}; -use hashql_diagnostics::DiagnosticIssues; - -use self::error::{ - BindingMode, InvalidTypeExpressionKind, SpecialFormExpanderDiagnosticIssues, - duplicate_closure_generic, duplicate_closure_parameter, duplicate_generic_constraint, - field_index_out_of_bounds, field_literal_type_annotation, fn_generics_with_type_annotation, - fn_params_with_type_annotation, invalid_argument_length, invalid_binding_name_not_path, - invalid_field_literal_type, invalid_fn_generic_param, invalid_fn_generics_expression, - invalid_fn_params_expression, invalid_generic_argument_path, invalid_generic_argument_type, - invalid_let_name_qualified_path, invalid_path_in_use_binding, invalid_type_call_function, - invalid_type_expression, invalid_type_name_qualified_path, invalid_use_import, - labeled_arguments_not_supported, type_with_existing_annotation, unknown_special_form_generics, - unknown_special_form_length, unknown_special_form_name, unsupported_type_constructor_function, - use_imports_with_type_annotation, use_path_with_generics, -}; -use crate::{ - node::{ - expr::{ - AsExpr, CallExpr, ClosureExpr, Expr, ExprKind, FieldExpr, IfExpr, IndexExpr, InputExpr, - LetExpr, NewTypeExpr, StructExpr, TupleExpr, TypeExpr, UseExpr, - call::Argument, - closure::{ClosureParam, ClosureSignature}, - r#use::{Glob, UseBinding, UseKind}, - }, - generic::{GenericArgument, GenericConstraint, GenericParam, Generics}, - id::NodeId, - path::{Path, PathSegmentArgument}, - r#type::{ - IntersectionType, StructField, StructType, TupleField, TupleType, Type, TypeKind, - UnionType, - }, - }, - visit::{Visitor, walk_expr}, -}; - -mod paths {} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, enum_iterator::Sequence)] -enum SpecialFormKind { - If, - As, - Let, - Type, - Newtype, - Use, - Fn, - Input, - Access, - Index, -} - -impl SpecialFormKind { - const fn as_str(self) -> &'static str { - match self { - Self::If => "if", - Self::As => "as", - Self::Let => "let", - Self::Type => "type", - Self::Newtype => "newtype", - Self::Use => "use", - Self::Fn => "fn", - Self::Input => "input", - Self::Access => "access", - Self::Index => "index", - } - } - - fn from_str(name: &str) -> Option { - match name { - "if" => Some(Self::If), - "as" => Some(Self::As), - "let" => Some(Self::Let), - "type" => Some(Self::Type), - "newtype" => Some(Self::Newtype), - "use" => Some(Self::Use), - "fn" => Some(Self::Fn), - "input" => Some(Self::Input), - "access" => Some(Self::Access), - "index" => Some(Self::Index), - _ => None, - } - } -} - -impl Display for SpecialFormKind { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.write_str(self.as_str()) - } -} - -pub struct SpecialFormExpander<'heap> { - heap: &'heap Heap, - diagnostics: SpecialFormExpanderDiagnosticIssues, -} - -impl<'heap> SpecialFormExpander<'heap> { - pub const fn new(heap: &'heap Heap) -> Self { - Self { - heap, - diagnostics: DiagnosticIssues::new(), - } - } - - pub fn take_diagnostics(&mut self) -> SpecialFormExpanderDiagnosticIssues { - mem::take(&mut self.diagnostics) - } - - fn lower_expr_to_type_call(&mut self, expr: CallExpr<'heap>) -> Option> { - const fn create_intersection_type<'heap>( - id: NodeId, - span: SpanId, - types: heap::Vec<'heap, Type<'heap>>, - ) -> TypeKind<'heap> { - TypeKind::Intersection(IntersectionType { id, span, types }) - } - - const fn create_union_type<'heap>( - id: NodeId, - span: SpanId, - types: heap::Vec<'heap, Type<'heap>>, - ) -> TypeKind<'heap> { - TypeKind::Union(UnionType { id, span, types }) - } - - let ExprKind::Path(path) = expr.function.kind else { - self.diagnostics - .push(invalid_type_call_function(expr.function.span)); - return None; - }; - - let constructor = if path.matches_absolute_path(["core", "bits", "and"]) { - // The `&` operator, which is internally overloaded for types to create intersections - create_intersection_type - } else if path.matches_absolute_path(["core", "bits", "or"]) { - // The `|` operator, which is internally overloaded for types to create unions - create_union_type - } else { - self.diagnostics - .push(unsupported_type_constructor_function(expr.function.span)); - return None; - }; - - let mut types = Vec::with_capacity_in(expr.arguments.len(), self.heap); - - let arguments_len = expr.arguments.len(); - for argument in expr.arguments { - let Some(r#type) = self.lower_expr_to_type(*argument.value) else { - continue; - }; - - types.push(r#type); - } - - if types.len() != arguments_len { - // An error occurred downstream, propagate said error - return None; - } - - Some(Type { - id: expr.id, - span: expr.span, - kind: constructor(expr.id, expr.span, types), - }) - } - - fn lower_expr_to_type_struct(&mut self, expr: StructExpr<'heap>) -> Option> { - if let Some(type_expr) = &expr.r#type { - self.diagnostics - .push(type_with_existing_annotation(type_expr.span)); - return None; - } - - let entries_len = expr.entries.len(); - let mut fields = Vec::with_capacity_in(entries_len, self.heap); - - for entry in expr.entries { - let Some(r#type) = self.lower_expr_to_type(*entry.value) else { - continue; - }; - - fields.push(StructField { - id: entry.id, - span: entry.span, - name: entry.key, - r#type, - }); - } - - if fields.len() != entries_len { - // Downstream an error happened so we're propagating the error - return None; - } - - Some(Type { - id: expr.id, - span: expr.span, - kind: TypeKind::Struct(StructType { - id: expr.id, - span: expr.span, - fields, - }), - }) - } - - fn lower_expr_to_type_tuple(&mut self, expr: TupleExpr<'heap>) -> Option> { - if let Some(type_expr) = &expr.r#type { - self.diagnostics - .push(type_with_existing_annotation(type_expr.span)); - return None; - } - - let elements_len = expr.elements.len(); - let mut fields = Vec::with_capacity_in(elements_len, self.heap); - - for element in expr.elements { - let Some(r#type) = self.lower_expr_to_type(*element.value) else { - continue; - }; - - fields.push(TupleField { - id: element.id, - span: element.span, - r#type, - }); - } - - if fields.len() != elements_len { - // Downstream an error happened, so we're propagating the error - return None; - } - - Some(Type { - id: expr.id, - span: expr.span, - kind: TypeKind::Tuple(TupleType { - id: expr.id, - span: expr.span, - fields, - }), - }) - } - - fn lower_expr_to_type(&mut self, expr: Expr<'heap>) -> Option> { - match expr.kind { - ExprKind::Call(call_expr) => self.lower_expr_to_type_call(call_expr), - ExprKind::Struct(struct_expr) => self.lower_expr_to_type_struct(struct_expr), - ExprKind::Tuple(tuple_expr) => self.lower_expr_to_type_tuple(tuple_expr), - ExprKind::Path(path) => Some(Type { - id: expr.id, - span: expr.span, - kind: TypeKind::Path(path), - }), - ExprKind::Underscore => Some(Type { - id: expr.id, - span: expr.span, - kind: TypeKind::Infer, - }), - kind @ (ExprKind::Dict(_) - | ExprKind::List(_) - | ExprKind::Literal(_) - | ExprKind::Let(_) - | ExprKind::Type(_) - | ExprKind::NewType(_) - | ExprKind::Use(_) - | ExprKind::Input(_) - | ExprKind::Closure(_) - | ExprKind::If(_) - | ExprKind::Field(_) - | ExprKind::Index(_) - | ExprKind::As(_) - | ExprKind::Dummy) => { - let kind_name = match kind { - ExprKind::Dict(_) => InvalidTypeExpressionKind::Dict, - ExprKind::List(_) => InvalidTypeExpressionKind::List, - ExprKind::Literal(_) => InvalidTypeExpressionKind::Literal, - ExprKind::Let(_) => InvalidTypeExpressionKind::Let, - ExprKind::Type(_) => InvalidTypeExpressionKind::Type, - ExprKind::NewType(_) => InvalidTypeExpressionKind::NewType, - ExprKind::Use(_) => InvalidTypeExpressionKind::Use, - ExprKind::Input(_) => InvalidTypeExpressionKind::Input, - ExprKind::Closure(_) => InvalidTypeExpressionKind::Closure, - ExprKind::If(_) => InvalidTypeExpressionKind::If, - ExprKind::Field(_) => InvalidTypeExpressionKind::Field, - ExprKind::Index(_) => InvalidTypeExpressionKind::Index, - ExprKind::As(_) => InvalidTypeExpressionKind::As, - ExprKind::Dummy => InvalidTypeExpressionKind::Dummy, - ExprKind::Call(_) - | ExprKind::Struct(_) - | ExprKind::Tuple(_) - | ExprKind::Path(_) - | ExprKind::Underscore => unreachable!(), - }; - - self.diagnostics - .push(invalid_type_expression(expr.span, kind_name)); - None - } - } - } - - /// Lowers an if/2 special form to an `IfExpr` with no else branch. - /// - /// The if/2 form has the syntax: `(if condition then-expr)` - /// and is transformed into an if expression with no else branch. - fn lower_if_2(call: CallExpr<'heap>) -> ExprKind<'heap> { - let [test, then] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - ExprKind::If(IfExpr { - id: call.id, - span: call.span, - test: test.value, - then: then.value, - r#else: None, - }) - } - - /// Lowers an if/3 special form to an `IfExpr` with an else branch. - /// - /// The if/3 form has the syntax: `(if condition then-expr else-expr)` - /// and is transformed into a complete if-then-else expression. - fn lower_if_3(call: CallExpr<'heap>) -> ExprKind<'heap> { - let [test, then, r#else] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - ExprKind::If(IfExpr { - id: call.id, - span: call.span, - test: test.value, - then: then.value, - r#else: Some(r#else.value), - }) - } - - /// Lowers an if special form to the appropriate `IfExpr` variant. - /// - /// There are two forms of the `if` special form: - /// - if/2: `(if condition then-expr)` - no else branch - /// - if/3: `(if condition then-expr else-expr)` - with else branch - /// - /// This function validates the argument count and delegates to the appropriate - /// specialized lowering function. - fn lower_if(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() == 2 { - Some(Self::lower_if_2(call)) - } else if call.arguments.len() == 3 { - Some(Self::lower_if_3(call)) - } else { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::If, - &call.arguments, - &[2, 3], - )); - - None - } - } - - /// Lowers an as/2 special form to an `AsExpr`. - /// - /// The as/2 form has the syntax: `(as value type-expr)` - /// and is transformed into a type assertion expression. This validates - /// that `value` conforms to the type specified by `type-expr`. - /// - /// The function first checks that exactly 2 arguments are provided, - /// then attempts to convert the second argument into a valid type expression. - fn lower_as(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 2 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::As, - &call.arguments, - &[2], - )); - - return None; - } - - let [value, r#type] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let r#type = self.lower_expr_to_type(*r#type.value)?; - - Some(ExprKind::As(AsExpr { - id: call.id, - span: call.span, - value: value.value, - r#type: Box::new_in(r#type, self.heap), - })) - } - - /// Attempts to extract a `Path` from an argument. - /// - /// This function is used by various special form lowering functions to extract - /// path expressions from arguments. It checks if the argument contains a path - /// expression and returns it if present, otherwise it adds a diagnostic and - /// returns `None`. - /// - /// The `mode` parameter is used for generating appropriate error diagnostics. - fn lower_argument_to_path( - &mut self, - mode: BindingMode, - argument: Argument<'heap>, - ) -> Option> { - let ExprKind::Path(path) = argument.value.kind else { - self.diagnostics - .push(invalid_binding_name_not_path(argument.value.span, mode)); - - return None; - }; - - Some(path) - } - - /// Attempts to extract an identifier from an argument. - /// - /// This function validates that the argument contains a simple path expression that - /// can be converted to an identifier. It first extracts a path using `lower_argument_to_path` - /// and then ensures the path is a simple identifier (not a qualified path). - /// - /// The `mode` parameter is used for generating appropriate error diagnostics. - fn lower_argument_to_ident( - &mut self, - mode: BindingMode, - argument: Argument<'heap>, - ) -> Option> { - let path = self.lower_argument_to_path(mode, argument)?; - let span = path.span; - - let Some(name) = path.into_ident() else { - self.diagnostics - .push(invalid_let_name_qualified_path(span, mode)); - - return None; - }; - - Some(name) - } - - /// Attempts to extract a field identifier from an argument. - /// - /// This function handles both named field access (using identifiers) and indexed field access - /// (using integer literals). For literals, it validates that they are integers without type - /// annotations and within usize bounds. Otherwise, it falls back to `lower_argument_to_ident` - /// for named field access. - /// - /// The `mode` parameter is used for generating appropriate error diagnostics. - fn lower_argument_to_field( - &mut self, - mode: BindingMode, - argument: Argument<'heap>, - ) -> Option> { - if let ExprKind::Literal(literal) = &argument.value.kind { - if let Some(r#type) = &literal.r#type { - self.diagnostics - .push(field_literal_type_annotation(r#type.span)); - } - - let Primitive::Integer(integer) = literal.kind else { - self.diagnostics - .push(invalid_field_literal_type(literal.span)); - return None; - }; - - if integer.as_usize().is_none() { - self.diagnostics - .push(field_index_out_of_bounds(literal.span)); - return None; - } - - return Some(Ident { - span: literal.span, - value: integer.as_symbol(), - kind: IdentKind::Lexical, - }); - } - - self.lower_argument_to_ident(mode, argument) - } - - fn lower_argument_to_generic_ident( - &mut self, - mode: BindingMode, - argument: Argument<'heap>, - ) -> Option<(Ident<'heap>, heap::Vec<'heap, PathSegmentArgument<'heap>>)> { - let path = self.lower_argument_to_path(mode, argument)?; - let span = path.span; - - let Ok(name) = path.into_generic_ident() else { - self.diagnostics - .push(invalid_type_name_qualified_path(span, mode)); - - return None; - }; - - Some(name) - } - - fn lower_path_segment_arguments_to_constraints( - &mut self, - arguments: heap::Vec<'heap, PathSegmentArgument<'heap>>, - ) -> Option>> { - let mut constraints = Vec::with_capacity_in(arguments.len(), self.heap); - - let mut seen = fast_hash_map_with_capacity(arguments.len()); - - for argument in arguments { - match argument { - PathSegmentArgument::Argument(GenericArgument { - id, - span, - ref r#type, - }) if let Type { - kind: TypeKind::Path(path), - .. - } = r#type.as_ref() - && let Some(&ident) = path.as_ident() => - { - if let Err(error) = seen.try_insert(ident.value, ident.span) { - self.diagnostics.push(duplicate_generic_constraint( - ident.span, - ident.value.as_str(), - *error.entry.get(), - )); - - continue; - } - - constraints.push(GenericConstraint { - id, - span, - name: ident, - bound: None, - }); - } - // Specialized errors for paths, to properly guide the user - PathSegmentArgument::Argument(GenericArgument { ref r#type, .. }) - if let Type { - kind: TypeKind::Path(path), - .. - } = r#type.as_ref() => - { - self.diagnostics - .push(invalid_generic_argument_path(path.span)); - - return None; - } - PathSegmentArgument::Argument(generic_argument) => { - self.diagnostics - .push(invalid_generic_argument_type(generic_argument.r#type.span)); - - return None; - } - PathSegmentArgument::Constraint(generic_constraint) => { - if let Err(error) = - seen.try_insert(generic_constraint.name.value, generic_constraint.name.span) - { - self.diagnostics.push(duplicate_generic_constraint( - generic_constraint.span, - generic_constraint.name.value.as_str(), - *error.entry.get(), - )); - - continue; - } - - constraints.push(generic_constraint); - } - } - } - - Some(constraints) - } - - /// Lowers a let/3 special form to a `LetExpr` without type annotation. - /// - /// The let/3 form has the syntax: `(let name value body)` - /// and is transformed into a let expression with no explicit type annotation. - fn lower_let_3(&mut self, call: CallExpr<'heap>) -> Option> { - let [name, value, body] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - let name = self.lower_argument_to_ident(BindingMode::Let, name)?; - - Some(ExprKind::Let(LetExpr { - id: call.id, - span: call.span, - name, - value: value.value, - r#type: None, - body: body.value, - })) - } - - /// Lowers a let/4 special form to a `LetExpr` with an explicit type annotation. - /// - /// The let/4 form has the syntax: `(let name type value body)` - /// and is transformed into a let expression with an explicit type annotation. - fn lower_let_4(&mut self, call: CallExpr<'heap>) -> Option> { - let [name, r#type, value, body] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - let (name, r#type) = Option::zip( - self.lower_argument_to_ident(BindingMode::Let, name), - self.lower_expr_to_type(*r#type.value), - )?; - - Some(ExprKind::Let(LetExpr { - id: call.id, - span: call.span, - name, - value: value.value, - r#type: Some(Box::new_in(r#type, self.heap)), - body: body.value, - })) - } - - /// Lowers a `let` special form to the appropriate `LetExpr` variant. - /// - /// There are two forms of the `let` special form: - /// - let/3: `(let name value body)` - no type annotation - /// - let/4: `(let name type value body)` - with type annotation - /// - /// This function validates the argument count and delegates to the appropriate - /// specialized lowering function. - fn lower_let(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() == 3 { - self.lower_let_3(call) - } else if call.arguments.len() == 4 { - self.lower_let_4(call) - } else { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Let, - &call.arguments, - &[3, 4], - )); - - None - } - } - - /// Lowers a type/3 special form to a `TypeExpr`. - /// - /// The type/3 form has the syntax: `(type name type-expr body)` - /// and is transformed into a type expression that defines a type alias. - fn lower_type(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 3 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Type, - &call.arguments, - &[3], - )); - - return None; - } - - let [name, value, body] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let ((name, arguments), value) = Option::zip( - self.lower_argument_to_generic_ident(BindingMode::Type, name), - self.lower_expr_to_type(*value.value), - )?; - - let constraints = self.lower_path_segment_arguments_to_constraints(arguments)?; - - Some(ExprKind::Type(TypeExpr { - id: call.id, - span: call.span, - - name, - constraints, - - value: Box::new_in(value, self.heap), - body: body.value, - })) - } - - /// Lowers a newtype/3 special form to a `NewTypeExpr`. - /// - /// The newtype/3 form has the syntax: `(newtype name type-expr body)` - /// and is transformed into a newtype expression that defines a new type - /// with the structure of the provided type expression. - fn lower_newtype(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 3 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Newtype, - &call.arguments, - &[3], - )); - - return None; - } - - let [name, value, body] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let ((name, arguments), value) = Option::zip( - self.lower_argument_to_generic_ident(BindingMode::Newtype, name), - self.lower_expr_to_type(*value.value), - )?; - - let constraints = self.lower_path_segment_arguments_to_constraints(arguments)?; - - Some(ExprKind::NewType(NewTypeExpr { - id: call.id, - span: call.span, - - name, - constraints, - - value: Box::new_in(value, self.heap), - body: body.value, - })) - } - - /// Processes a struct expression for use imports. - /// - /// Struct-style imports have the form `{key: value, ...}` where each key represents the - /// name to bind and each value is either an underscore (to use the same name) or - /// an identifier (to use as alias). - /// - /// Returns a `UseKind::Named` with the appropriate bindings if successful. - fn lower_use_imports_struct(&mut self, r#struct: StructExpr<'heap>) -> Option> { - // {key: value}, each value must be a value must be an underscore *or* ident - if let Some(type_expr) = &r#struct.r#type { - self.diagnostics - .push(use_imports_with_type_annotation(type_expr.span)); - return None; - } - - let entries_len = r#struct.entries.len(); - let mut bindings = Vec::with_capacity_in(entries_len, self.heap); - - for entry in r#struct.entries { - let path = match entry.value.kind { - ExprKind::Path(path) => path, - ExprKind::Underscore => { - bindings.push(UseBinding { - id: entry.id, - span: entry.span, - name: entry.key, - alias: None, - }); - - continue; - } - ExprKind::Call(_) - | ExprKind::Struct(_) - | ExprKind::Dict(_) - | ExprKind::Tuple(_) - | ExprKind::List(_) - | ExprKind::Literal(_) - | ExprKind::Let(_) - | ExprKind::Type(_) - | ExprKind::NewType(_) - | ExprKind::Use(_) - | ExprKind::Input(_) - | ExprKind::Closure(_) - | ExprKind::If(_) - | ExprKind::Field(_) - | ExprKind::Index(_) - | ExprKind::As(_) - | ExprKind::Dummy => { - self.diagnostics.push(invalid_use_import(entry.value.span)); - continue; - } - }; - - let path_span = path.span; - let Some(alias) = path.into_ident() else { - self.diagnostics - .push(invalid_path_in_use_binding(path_span)); - continue; - }; - - bindings.push(UseBinding { - id: entry.id, - span: entry.span, - name: entry.key, - alias: Some(alias), - }); - } - - if bindings.len() != entries_len { - // error occurred downstream, propagate said error - return None; - } - - Some(UseKind::Named(bindings)) - } - - /// Processes a tuple expression for use imports. - /// - /// Tuple-style imports have the form `(path1, path2, ...)` where each path - /// should be a simple identifier. These imports use the original name without aliasing. - /// - /// Returns a `UseKind::Named` with the appropriate bindings if successful. - fn lower_use_imports_tuple(&mut self, tuple: TupleExpr<'heap>) -> Option> { - if let Some(type_expr) = &tuple.r#type { - self.diagnostics - .push(use_imports_with_type_annotation(type_expr.span)); - return None; - } - - let elements_len = tuple.elements.len(); - let mut bindings = Vec::with_capacity_in(elements_len, self.heap); - - for element in tuple.elements { - let ExprKind::Path(path) = element.value.kind else { - self.diagnostics - .push(invalid_use_import(element.value.span)); - continue; - }; - - let path_span = path.span; - let Some(name) = path.into_ident() else { - self.diagnostics - .push(invalid_path_in_use_binding(path_span)); - continue; - }; - - bindings.push(UseBinding { - id: element.id, - span: element.span, - name, - alias: None, - }); - } - - if bindings.len() != elements_len { - // error occurred downstream, propagate said error - return None; - } - - Some(UseKind::Named(bindings)) - } - - /// Processes use imports in different forms. - /// - /// This function handles three forms of imports: - /// - `*` (symbol) - Import all items (glob import) - /// - Struct-style imports - Named imports with potential aliases - /// - Tuple-style imports - Named imports without aliases - /// - /// Returns the appropriate `UseKind` variant based on the import form. - fn lower_use_imports(&mut self, argument: Argument<'heap>) -> Option> { - match argument.value.kind { - ExprKind::Path(path) => { - let path_id = path.id; - - let path_span = path.span; - if let Some(ident) = path.into_ident() { - if ident.value.as_str() == "*" { - return Some(UseKind::Glob(Glob { - id: path_id, - span: ident.span, - })); - } - - self.diagnostics.push(invalid_use_import(ident.span)); - return None; - } - - self.diagnostics - .push(invalid_path_in_use_binding(path_span)); - None - } - ExprKind::Struct(r#struct) => self.lower_use_imports_struct(r#struct), - ExprKind::Tuple(tuple) => self.lower_use_imports_tuple(tuple), - ExprKind::Call(_) - | ExprKind::Dict(_) - | ExprKind::List(_) - | ExprKind::Literal(_) - | ExprKind::Let(_) - | ExprKind::Type(_) - | ExprKind::NewType(_) - | ExprKind::Use(_) - | ExprKind::Input(_) - | ExprKind::Closure(_) - | ExprKind::If(_) - | ExprKind::Field(_) - | ExprKind::Index(_) - | ExprKind::As(_) - | ExprKind::Underscore - | ExprKind::Dummy => { - self.diagnostics - .push(invalid_use_import(argument.value.span)); - None - } - } - } - - /// Lowers a use/3 special form to a `UseExpr`. - /// - /// The use/3 form has the syntax: `(use path imports body)` - /// where path is the module path to import from, imports specifies - /// what to import, and body is the expression in which the imports are available. - /// - /// This function verifies the argument count and structure, then processes - /// the path and imports to create a `UseExpr`. - fn lower_use(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 3 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Use, - &call.arguments, - &[3], - )); - return None; - } - - let [path, imports, body] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let (path, kind) = Option::zip( - self.lower_argument_to_path(BindingMode::Use, path), - self.lower_use_imports(imports), - )?; - - if path.has_generic_arguments() { - self.diagnostics - .push(use_path_with_generics(path.span, &path)); - - return None; - } - - Some(ExprKind::Use(UseExpr { - id: call.id, - span: call.span, - path, - kind, - body: body.value, - })) - } - - /// Processes a tuple expression for function generic parameters. - /// - /// This function extracts generic parameter names from a tuple expression - /// with the form `(param1, param2, ...)` where each element is a path - /// that can be converted to an identifier. - /// - /// Returns a `Generics` instance containing the generic parameters with no bounds. - fn lower_fn_generics_tuple(&mut self, tuple: TupleExpr<'heap>) -> Option> { - if let Some(type_expr) = &tuple.r#type { - self.diagnostics - .push(fn_generics_with_type_annotation(type_expr.span)); - return None; - } - - let elements_len = tuple.elements.len(); - let mut params = Vec::with_capacity_in(elements_len, self.heap); - - for element in tuple.elements { - let ExprKind::Path(path) = element.value.kind else { - self.diagnostics - .push(invalid_fn_generics_expression(element.value.span)); - continue; - }; - - let path_span = path.span; - let Some(name) = path.into_ident() else { - self.diagnostics.push(invalid_fn_generic_param(path_span)); - continue; - }; - - params.push(GenericParam { - id: element.id, - span: element.span, - name, - bound: None, - }); - } - - if params.len() != elements_len { - // Downstream an error happened which we catch - return None; - } - - Some(Generics { - id: tuple.id, - span: tuple.span, - params, - }) - } - - /// Processes a struct expression for function generic parameters with bounds. - /// - /// This function extracts generic parameters from a struct expression with the form - /// `(param1: bound1, param2: _, ...)` where each key is a parameter name and each - /// value is either an underscore (for no bound) or a type expression (for a bound). - /// - /// Returns a `Generics` instance containing the generic parameters with their bounds. - fn lower_fn_generics_struct(&mut self, r#struct: StructExpr<'heap>) -> Option> { - if let Some(type_expr) = &r#struct.r#type { - self.diagnostics - .push(fn_generics_with_type_annotation(type_expr.span)); - return None; - } - - let entries_len = r#struct.entries.len(); - let mut params = Vec::with_capacity_in(entries_len, self.heap); - - for entry in r#struct.entries { - let bound = if matches!(entry.value.kind, ExprKind::Underscore) { - None - } else if let Some(bound) = self.lower_expr_to_type(*entry.value) { - Some(Box::new_in(bound, self.heap)) - } else { - continue; - }; - - params.push(GenericParam { - id: entry.id, - span: entry.span, - name: entry.key, - bound, - }); - } - - if params.len() != entries_len { - // Downstream an error happened which we catch - return None; - } - - Some(Generics { - id: r#struct.id, - span: r#struct.span, - params, - }) - } - - /// Processes an argument for function generic parameters. - /// - /// This function handles two forms of generic parameter declarations: - /// - Tuple form: Simple parameters without bounds - /// - Struct form: Parameters with optional type bounds - /// - /// Returns a `Generics` instance if successful. - fn lower_fn_generics(&mut self, argument: Argument<'heap>) -> Option> { - match argument.value.kind { - ExprKind::Tuple(tuple) => self.lower_fn_generics_tuple(tuple), - ExprKind::Struct(r#struct) => self.lower_fn_generics_struct(r#struct), - ExprKind::Call(_) - | ExprKind::Dict(_) - | ExprKind::List(_) - | ExprKind::Literal(_) - | ExprKind::Path(_) - | ExprKind::Let(_) - | ExprKind::Type(_) - | ExprKind::NewType(_) - | ExprKind::Use(_) - | ExprKind::Input(_) - | ExprKind::Closure(_) - | ExprKind::If(_) - | ExprKind::Field(_) - | ExprKind::Index(_) - | ExprKind::As(_) - | ExprKind::Underscore - | ExprKind::Dummy => { - self.diagnostics - .push(invalid_fn_generics_expression(argument.value.span)); - None - } - } - } - - /// Processes a struct expression for function parameters. - /// - /// This function extracts parameter names and type bounds from a struct expression - /// with the form `(param1: type1, param2: type2, ...)` where each key is a parameter name - /// and each value is a type expression representing the parameter type. - /// - /// Returns a vector of `ClosureParam` instances if successful. - fn lower_fn_parameters( - &mut self, - argument: Argument<'heap>, - ) -> Option>> { - let ExprKind::Struct(r#struct) = argument.value.kind else { - self.diagnostics - .push(invalid_fn_params_expression(argument.value.span)); - return None; - }; - - if let Some(type_expr) = &r#struct.r#type { - self.diagnostics - .push(fn_params_with_type_annotation(type_expr.span)); - return None; - } - - let entries_len = r#struct.entries.len(); - let mut params = Vec::with_capacity_in(entries_len, self.heap); - - for entry in r#struct.entries { - let Some(bound) = self.lower_expr_to_type(*entry.value) else { - continue; - }; - - params.push(ClosureParam { - id: entry.id, - span: entry.span, - name: entry.key, - bound: Box::new_in(bound, self.heap), - }); - } - - if params.len() != entries_len { - // downstream we've received an error, propagate said error - return None; - } - - Some(params) - } - - /// Lowers a fn/4 special form to a `ClosureExpr`. - /// - /// The fn/4 form has the syntax: `(fn generics params return-type body)` - /// where: - /// - `generics` defines type parameters - /// - `params` defines function parameters and their types - /// - `return-type` specifies the function return type - /// - `body` is the function implementation - /// - /// This function processes the arguments to create a function expression. - fn lower_fn(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 4 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Fn, - &call.arguments, - &[4], - )); - - return None; - } - - let [generics, params, return_type, body] = - call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let ((generics, params), return_type) = self - .lower_fn_generics(generics) - .zip(self.lower_fn_parameters(params)) - .zip(self.lower_expr_to_type(*return_type.value))?; - - let mut seen = FastHashMap::with_capacity_and_hasher( - generics.params.len().max(params.len()), - foldhash::fast::RandomState::default(), - ); - - for param in &generics.params { - if let Err(error) = seen.try_insert(param.name.value, param.name.span) { - self.diagnostics.push(duplicate_closure_generic( - param.name.span, - param.name.value.as_str(), - *error.entry.get(), - )); - } - } - - seen.clear(); - - for param in ¶ms { - if let Err(error) = seen.try_insert(param.name.value, param.name.span) { - self.diagnostics.push(duplicate_closure_parameter( - param.name.span, - param.name.value.as_str(), - *error.entry.get(), - )); - } - } - - let signature = ClosureSignature { - id: NodeId::PLACEHOLDER, - // TODO: ideally we'd like to merge the span of `generics`, `params`, and `return_type` - // into one. - span: call.span, - generics, - inputs: params, - output: Box::new_in(return_type, self.heap), - }; - - Some(ExprKind::Closure(ClosureExpr { - id: call.id, - span: call.span, - signature: Box::new_in(signature, self.heap), - body: body.value, - })) - } - - /// Lowers an input/2 special form to an `InputExpr` without a default value. - /// - /// The input/2 form has the syntax: `(input name type)` - /// and is transformed into an input expression that defines a required input. - fn lower_input_2(&mut self, call: CallExpr<'heap>) -> Option> { - let [name, r#type] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - let (name, r#type) = Option::zip( - self.lower_argument_to_ident(BindingMode::Input, name), - self.lower_expr_to_type(*r#type.value), - )?; - - Some(ExprKind::Input(InputExpr { - id: call.id, - span: call.span, - name, - r#type: Box::new_in(r#type, self.heap), - default: None, - })) - } - - /// Lowers an input/3 special form to an `InputExpr` with a default value. - /// - /// The input/3 form has the syntax: `(input name type default)` - /// and is transformed into an input expression with a default value. - fn lower_input_3(&mut self, call: CallExpr<'heap>) -> Option> { - let [name, r#type, default] = call - .arguments - .try_into() - .expect("The caller should've verified the length of the arguments"); - - let (name, r#type) = Option::zip( - self.lower_argument_to_ident(BindingMode::Input, name), - self.lower_expr_to_type(*r#type.value), - )?; - - Some(ExprKind::Input(InputExpr { - id: call.id, - span: call.span, - name, - r#type: Box::new_in(r#type, self.heap), - default: Some(default.value), - })) - } - - /// Lowers an input special form to the appropriate `InputExpr` variant. - /// - /// There are two forms of the `input` special form: - /// - input/2: `(input name type)` - defines a required input - /// - input/3: `(input name type default)` - defines an input with a default value - /// - /// This function validates the argument count and delegates to the appropriate - /// specialized lowering function. - fn lower_input(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() == 2 { - self.lower_input_2(call) - } else if call.arguments.len() == 3 { - self.lower_input_3(call) - } else { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Input, - &call.arguments, - &[2, 3], - )); - - None - } - } - - /// Lowers an access/2 special form to a `FieldExpr`. - /// - /// The access/2 form has the syntax: `(access object field)` - /// and is transformed into a field access expression that retrieves - /// the specified field from the object. - /// - /// This is equivalent to the dot notation `object.field` in many languages. - fn lower_access(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 2 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Access, - &call.arguments, - &[2], - )); - - return None; - } - - let [body, field] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - let field = self.lower_argument_to_field(BindingMode::Access, field)?; - - Some(ExprKind::Field(FieldExpr { - id: call.id, - span: call.span, - value: body.value, - field, - })) - } - - /// Lowers an index/2 special form to an `IndexExpr`. - /// - /// The index/2 form has the syntax: `(index collection index)` - /// and is transformed into an index access expression that retrieves - /// the element at the specified index from the collection. - /// - /// This is equivalent to the bracket notation `collection[index]` in many languages. - fn lower_index(&mut self, call: CallExpr<'heap>) -> Option> { - if call.arguments.len() != 2 { - self.diagnostics.push(invalid_argument_length( - call.span, - SpecialFormKind::Index, - &call.arguments, - &[2], - )); - return None; - } - - let [body, index] = call.arguments.try_into().unwrap_or_else(|_| unreachable!()); - - Some(ExprKind::Index(IndexExpr { - id: call.id, - span: call.span, - value: body.value, - index: index.value, - })) - } -} - -impl<'heap> Visitor<'heap> for SpecialFormExpander<'heap> { - fn visit_expr(&mut self, expr: &mut Expr<'heap>) { - // First we walk the whole expression tree, and only then do we expand ourselves. - walk_expr(self, expr); - - let ExprKind::Call(call) = &mut expr.kind else { - return; - }; - - let ExprKind::Path(path) = &call.function.kind else { - return; - }; - - // We're not checking for arguments, as these will result in an error later - if !path.starts_with_absolute_path(["kernel", "special_form"], false) { - return; - } - - // Anything below here means that we're dealing with a special form, therefore anytime we - // error out we replace the kind with a dummy expression. This allows us to continue - // processing the rest of the expression tree during the different phases of lowering. - - let ExprKind::Call(call) = mem::replace(&mut expr.kind, ExprKind::Dummy) else { - // We're verified before that this is a call expression - unreachable!() - }; - - let ExprKind::Path(path) = &call.function.kind else { - // We're verified before that this is a path expression - unreachable!() - }; - - if !call.labeled_arguments.is_empty() { - self.diagnostics.push(labeled_arguments_not_supported( - call.span, - &call.labeled_arguments, - )); - - return; - } - - if path.segments.len() != 3 { - // Special form path is always exactly three segments long - self.diagnostics - .push(unknown_special_form_length(path.span, path)); - - return; - } - - if path.has_generic_arguments() { - let mut arguments = Vec::new(); - - for segment in &path.segments { - arguments.extend(&segment.arguments); - } - - self.diagnostics - .push(unknown_special_form_generics(&arguments)); - - return; - } - - let function = &path.segments[2].name; - - let Some(special_form) = SpecialFormKind::from_str(function.value.as_str()) else { - self.diagnostics - .push(unknown_special_form_name(path.span, path)); - - return; - }; - - let kind = match special_form { - SpecialFormKind::If => self.lower_if(call), - SpecialFormKind::As => self.lower_as(call), - SpecialFormKind::Let => self.lower_let(call), - SpecialFormKind::Type => self.lower_type(call), - SpecialFormKind::Newtype => self.lower_newtype(call), - SpecialFormKind::Use => self.lower_use(call), - SpecialFormKind::Fn => self.lower_fn(call), - SpecialFormKind::Input => self.lower_input(call), - SpecialFormKind::Access => self.lower_access(call), - SpecialFormKind::Index => self.lower_index(call), - }; - - if let Some(kind) = kind { - expr.kind = kind; - } - } -} - -#[cfg(test)] -mod tests { - use enum_iterator::all; - - use super::*; - - #[test] - fn special_form_name_bidirectional() { - // Verify that for every variant, from_str(as_str(variant)) == Some(variant) - for variant in all::() { - let name = variant.as_str(); - let parsed = SpecialFormKind::from_str(name); - - assert_eq!(parsed, Some(variant)); - } - } -} diff --git a/libs/@local/hashql/ast/src/node/expr/as.rs b/libs/@local/hashql/ast/src/node/expr/as.rs index 03de6752510..db889fa5410 100644 --- a/libs/@local/hashql/ast/src/node/expr/as.rs +++ b/libs/@local/hashql/ast/src/node/expr/as.rs @@ -27,7 +27,7 @@ use crate::node::{id::NodeId, r#type::Type}; /// value as String /// value as {name: String, age: Int} /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct AsExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/call.rs b/libs/@local/hashql/ast/src/node/expr/call.rs index e4dc0b8cf47..07819f50ebd 100644 --- a/libs/@local/hashql/ast/src/node/expr/call.rs +++ b/libs/@local/hashql/ast/src/node/expr/call.rs @@ -19,13 +19,13 @@ use crate::node::id::NodeId; /// For this function call, there are two `Argument` instances: /// - One for the expression `x` /// - One for the expression `+(y, 1)` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct Argument<'heap> { // TODO: we might be able to remove these pub id: NodeId, pub span: SpanId, - pub value: heap::Box<'heap, Expr<'heap>>, + pub value: Expr<'heap>, } /// A labeled (named) argument passed to a function call. @@ -46,7 +46,7 @@ pub struct Argument<'heap> { /// - One for the label `:x` with the value `1` /// - One for the label `:y` with the value `2` /// - One for the label `:z` with the value `3` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct LabeledArgument<'heap> { pub id: NodeId, pub span: SpanId, @@ -91,7 +91,7 @@ pub struct LabeledArgument<'heap> { /// let func = if condition then fn1 else fn2 in /// func(value) /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct CallExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/closure.rs b/libs/@local/hashql/ast/src/node/expr/closure.rs index d72a0237d9c..196f82db0ce 100644 --- a/libs/@local/hashql/ast/src/node/expr/closure.rs +++ b/libs/@local/hashql/ast/src/node/expr/closure.rs @@ -6,13 +6,13 @@ use crate::node::{generic::Generics, id::NodeId, r#type::Type}; /// A parameter declaration for a closure. /// /// Represents a named parameter with an associated type in a closure's signature. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct ClosureParam<'heap> { pub id: NodeId, pub span: SpanId, pub name: Ident<'heap>, - pub bound: heap::Box<'heap, Type<'heap>>, + pub bound: Type<'heap>, } /// The signature of a closure. @@ -20,7 +20,7 @@ pub struct ClosureParam<'heap> { /// Defines the interface of a closure function, including its generic type parameters, /// input parameters, and return type. The signature provides all type information /// necessary for type checking and validation of the closure. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct ClosureSignature<'heap> { pub id: NodeId, pub span: SpanId, @@ -28,7 +28,7 @@ pub struct ClosureSignature<'heap> { pub generics: Generics<'heap>, pub inputs: heap::Vec<'heap, ClosureParam<'heap>>, - pub output: heap::Box<'heap, Type<'heap>>, + pub output: Type<'heap>, } /// A closure expression in the HashQL Abstract Syntax Tree. @@ -56,7 +56,7 @@ pub struct ClosureSignature<'heap> { /// fn(x: T, y: T): T => *(x, y) /// fn(x: T, y: T): T => *(x, y) /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct ClosureExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/dict.rs b/libs/@local/hashql/ast/src/node/expr/dict.rs index e3fb3a74f0f..2e81197a8ee 100644 --- a/libs/@local/hashql/ast/src/node/expr/dict.rs +++ b/libs/@local/hashql/ast/src/node/expr/dict.rs @@ -8,7 +8,7 @@ use crate::node::{id::NodeId, r#type::Type}; /// Represents a single key-value pair in a dictionary expression. /// Both the key and value are expressions that will be evaluated /// when the dictionary is created. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct DictEntry<'heap> { pub id: NodeId, pub span: SpanId, @@ -38,7 +38,7 @@ pub struct DictEntry<'heap> { /// ```text /// {"key1": value1, "key2": value2} /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct DictExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/field.rs b/libs/@local/hashql/ast/src/node/expr/field.rs index 371573863a8..84f23e2baf4 100644 --- a/libs/@local/hashql/ast/src/node/expr/field.rs +++ b/libs/@local/hashql/ast/src/node/expr/field.rs @@ -24,7 +24,7 @@ use crate::node::id::NodeId; /// ```text /// user.name /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct FieldExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/if.rs b/libs/@local/hashql/ast/src/node/expr/if.rs index cd8be7152bc..648f6c13117 100644 --- a/libs/@local/hashql/ast/src/node/expr/if.rs +++ b/libs/@local/hashql/ast/src/node/expr/if.rs @@ -32,7 +32,7 @@ use crate::node::id::NodeId; /// if has_permission /// then perform_action() /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct IfExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/index.rs b/libs/@local/hashql/ast/src/node/expr/index.rs index d57a2c24afe..c63ed42dd6d 100644 --- a/libs/@local/hashql/ast/src/node/expr/index.rs +++ b/libs/@local/hashql/ast/src/node/expr/index.rs @@ -32,7 +32,7 @@ use crate::node::id::NodeId; /// items[0] /// matrix[i][j] /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct IndexExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/input.rs b/libs/@local/hashql/ast/src/node/expr/input.rs index d0eeefec605..2b77aa1898a 100644 --- a/libs/@local/hashql/ast/src/node/expr/input.rs +++ b/libs/@local/hashql/ast/src/node/expr/input.rs @@ -28,7 +28,7 @@ use crate::node::{id::NodeId, r#type::Type}; /// input(limit, Int) /// input(limit, Int, 10) /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct InputExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/let.rs b/libs/@local/hashql/ast/src/node/expr/let.rs index 5efa2ade134..46eb8d5c199 100644 --- a/libs/@local/hashql/ast/src/node/expr/let.rs +++ b/libs/@local/hashql/ast/src/node/expr/let.rs @@ -27,7 +27,7 @@ use crate::node::{id::NodeId, r#type::Type}; /// let x = 42 in /// let x: Int = 42 in /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct LetExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/list.rs b/libs/@local/hashql/ast/src/node/expr/list.rs index c791f4125be..57f1c716c6c 100644 --- a/libs/@local/hashql/ast/src/node/expr/list.rs +++ b/libs/@local/hashql/ast/src/node/expr/list.rs @@ -7,7 +7,7 @@ use crate::node::{id::NodeId, r#type::Type}; /// /// Represents a single value in a list, containing the expression that /// produces the element value. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct ListElement<'heap> { pub id: NodeId, pub span: SpanId, @@ -36,7 +36,7 @@ pub struct ListElement<'heap> { /// [value1, value2, value3] /// [value1, value2, value3] as List /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct ListExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/mod.rs b/libs/@local/hashql/ast/src/node/expr/mod.rs index 066dcb7e747..fd62deace95 100644 --- a/libs/@local/hashql/ast/src/node/expr/mod.rs +++ b/libs/@local/hashql/ast/src/node/expr/mod.rs @@ -59,7 +59,7 @@ pub use self::{ r#as::AsExpr, call::CallExpr, closure::ClosureExpr, dict::DictExpr, field::FieldExpr, r#if::IfExpr, index::IndexExpr, input::InputExpr, r#let::LetExpr, list::ListExpr, literal::LiteralExpr, newtype::NewTypeExpr, r#struct::StructExpr, tuple::TupleExpr, - r#type::TypeExpr, r#use::UseExpr, + r#type::TypeExpr, }; use super::{id::NodeId, path::Path}; @@ -72,7 +72,7 @@ use super::{id::NodeId, path::Path}; /// The examples below demonstrate the `JExpr` syntax (JSON-based frontend), as well as a fictional /// "documentation syntax" (used for readability) for each expression kind. Remember that these are /// just frontend representations - the AST itself is independent of any particular syntax. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub enum ExprKind<'heap> { /// A function call expression. /// @@ -320,31 +320,6 @@ pub enum ExprKind<'heap> { /// ``` NewType(NewTypeExpr<'heap>), - /// A module import expression (special form). - /// - /// Imports symbols from another module into the current scope. This is expanded - /// from a function call during AST transformation. HashQL supports selective - /// imports with optional renaming. - /// - /// # Examples - /// - /// ## J-Expr - /// - /// ```json - /// ["use", "path::to::module", {"#struct": {"item1": "_", "original": "renamed"}}, ] - /// ["use", "path::to::module", {"#tuple": ["item1", "original"]}, ] - /// ["use", "path::to::module", "*", ] - /// ``` - /// - /// ## Documentation Format - /// - /// ```text - /// use path::to::module::{item1, original as renamed} in - /// use path::to::module::{item1, original} in - /// use path::to::module::* in - /// ``` - Use(UseExpr<'heap>), - /// An input parameter declaration (special form). /// /// Declares an input parameter for a function or query. This is expanded @@ -544,7 +519,7 @@ pub enum ExprKind<'heap> { /// Each expression has a unique identifier and a span that points to its /// location in the source code, which are crucial for error reporting, /// debugging, and tracking nodes through transformation phases. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct Expr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/newtype.rs b/libs/@local/hashql/ast/src/node/expr/newtype.rs index 2a6d61866f8..f2ed36d4cc2 100644 --- a/libs/@local/hashql/ast/src/node/expr/newtype.rs +++ b/libs/@local/hashql/ast/src/node/expr/newtype.rs @@ -36,7 +36,7 @@ use crate::node::{generic::GenericConstraint, id::NodeId, r#type::Type}; /// newtype AccountId = String in /// AccountId("1234") /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct NewTypeExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/struct.rs b/libs/@local/hashql/ast/src/node/expr/struct.rs index e0bd790194b..37a38b081f2 100644 --- a/libs/@local/hashql/ast/src/node/expr/struct.rs +++ b/libs/@local/hashql/ast/src/node/expr/struct.rs @@ -7,13 +7,13 @@ use crate::node::{id::NodeId, r#type::Type}; /// /// A struct entry consists of a named field (represented by an identifier) /// and its corresponding value expression. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct StructEntry<'heap> { pub id: NodeId, pub span: SpanId, pub key: Ident<'heap>, - pub value: heap::Box<'heap, Expr<'heap>>, + pub value: Expr<'heap>, } /// A struct expression in the HashQL Abstract Syntax Tree. @@ -42,7 +42,7 @@ pub struct StructEntry<'heap> { /// (:) /// (field1: value1, field2: value2) /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct StructExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/tuple.rs b/libs/@local/hashql/ast/src/node/expr/tuple.rs index 410b3ab5228..109fa17a2ab 100644 --- a/libs/@local/hashql/ast/src/node/expr/tuple.rs +++ b/libs/@local/hashql/ast/src/node/expr/tuple.rs @@ -7,12 +7,12 @@ use crate::node::{id::NodeId, r#type::Type}; /// /// Represents a single value in a tuple, containing the expression that /// produces the element value. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct TupleElement<'heap> { pub id: NodeId, pub span: SpanId, - pub value: heap::Box<'heap, Expr<'heap>>, + pub value: Expr<'heap>, } /// A tuple expression in the HashQL Abstract Syntax Tree. @@ -42,7 +42,7 @@ pub struct TupleElement<'heap> { /// () /// (value1, value2, value3) /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct TupleExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/type.rs b/libs/@local/hashql/ast/src/node/expr/type.rs index 06f4ce0c21f..05efdadaf4f 100644 --- a/libs/@local/hashql/ast/src/node/expr/type.rs +++ b/libs/@local/hashql/ast/src/node/expr/type.rs @@ -28,7 +28,7 @@ use crate::node::{generic::GenericConstraint, id::NodeId, r#type::Type}; /// type UserId = String in /// type Point = {x: Float, y: Float} in /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug)] pub struct TypeExpr<'heap> { pub id: NodeId, pub span: SpanId, diff --git a/libs/@local/hashql/ast/src/node/expr/use.rs b/libs/@local/hashql/ast/src/node/expr/use.rs index 983a759d99b..1b7465ec2d5 100644 --- a/libs/@local/hashql/ast/src/node/expr/use.rs +++ b/libs/@local/hashql/ast/src/node/expr/use.rs @@ -1,7 +1,6 @@ use hashql_core::{heap, span::SpanId, symbol::Ident}; -use super::Expr; -use crate::node::{id::NodeId, path::Path}; +use crate::node::id::NodeId; /// A binding for an imported symbol. /// @@ -36,67 +35,3 @@ pub enum UseKind<'heap> { Named(heap::Vec<'heap, UseBinding<'heap>>), Glob(Glob), } - -/// A module import expression in the HashQL Abstract Syntax Tree. -/// -/// Represents a `use` declaration that imports symbols from another module -/// into the current scope. Imports can bring in specific named items or all -/// exported items from a module. -/// -/// Imported symbols are only visible within the body expression. -/// -/// # Examples -/// -/// ## J-Expr -/// -/// ```json -/// // Named Import -/// ["use", "core::math", {"#tuple": ["sin", "cos", "tan"]}, -/// ["*", -/// ["sin", "angle"], -/// ["cos", "angle"] -/// ] -/// ] -/// -/// // Named import with renaming -/// ["use", "core::math", {"#tuple": {"sin": "_", "cos": "cosine", "tan": "_"}}, -/// ["*", -/// ["sin", "angle"], -/// ["cosine", "angle"] -/// ] -/// ] -/// -/// // Glob import -/// ["use", "core::math", "*", -/// ["*", -/// ["sin", "angle"], -/// ["cos", "angle"] -/// ] -/// ] -/// ``` -/// -/// ## Documentation Format -/// -/// ```text -/// // Named import -/// use math::{sin, cos, tan} in -/// *(sin(angle), cos(angle)) -/// -/// // Import with renaming -/// use math::{sin, cos as cosine, tan} in -/// *(sin(angle), cosine(angle)) -/// -/// // Glob import -/// use math::* in -/// *(sin(angle), cos(angle)) -/// ``` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct UseExpr<'heap> { - pub id: NodeId, - pub span: SpanId, - - pub path: Path<'heap>, - pub kind: UseKind<'heap>, - - pub body: heap::Box<'heap, Expr<'heap>>, -} diff --git a/libs/@local/hashql/ast/src/node/generic.rs b/libs/@local/hashql/ast/src/node/generic.rs index c2f9b849ee8..7301976646a 100644 --- a/libs/@local/hashql/ast/src/node/generic.rs +++ b/libs/@local/hashql/ast/src/node/generic.rs @@ -17,7 +17,7 @@ pub struct GenericArgument<'heap> { pub id: NodeId, pub span: SpanId, - pub r#type: heap::Box<'heap, Type<'heap>>, + pub r#type: Type<'heap>, } /// Represents a constraint applied to a generic type parameter. @@ -46,7 +46,7 @@ pub struct GenericConstraint<'heap> { pub name: Ident<'heap>, // Due to the fact that we have `&` and `|`, we don't need to have `Vec` of bounds - pub bound: Option>>, + pub bound: Option>, } /// A generic type parameter declaration. @@ -66,7 +66,7 @@ pub struct GenericParam<'heap> { pub span: SpanId, pub name: Ident<'heap>, - pub bound: Option>>, + pub bound: Option>, } /// A collection of generic parameters for a type or function. diff --git a/libs/@local/hashql/ast/src/node/path.rs b/libs/@local/hashql/ast/src/node/path.rs index 93cabe0d4f5..a02b8772f3c 100644 --- a/libs/@local/hashql/ast/src/node/path.rs +++ b/libs/@local/hashql/ast/src/node/path.rs @@ -163,6 +163,17 @@ impl<'heap> Path<'heap> { Some((segment.name, &segment.arguments)) } + pub fn as_generic_ident_mut( + &mut self, + ) -> Option<(Ident<'heap>, &mut [PathSegmentArgument<'heap>])> { + if !self.is_generic_ident() { + return None; + } + + let segment = &mut self.segments[0]; + Some((segment.name, &mut segment.arguments)) + } + pub(crate) fn into_ident(mut self) -> Option> { if !self.is_ident() { return None; @@ -188,35 +199,6 @@ impl<'heap> Path<'heap> { Ok((segment.name, segment.arguments)) } - /// Checks if this path is an absolute path that matches the provided sequence of identifiers. - /// - /// A path matches when: - /// - The path is absolute (rooted with `::`) - /// - It has the same number of segments as provided identifiers - /// - Each segment name matches the corresponding identifier - /// - None of the path segments have generic arguments - pub(crate) fn matches_absolute_path( - &self, - path: impl IntoIterator, - ) -> bool - where - T: AsRef, - { - if !self.rooted { - return false; - } - - let path = path.into_iter(); - - if self.segments.len() != path.len() { - return false; - } - - self.segments.iter().zip(path).all(|(segment, ident)| { - segment.name.value.as_str() == ident.as_ref() && segment.arguments.is_empty() - }) - } - pub(crate) fn starts_with_absolute_path( &self, path: impl IntoIterator, diff --git a/libs/@local/hashql/ast/src/node/type/mod.rs b/libs/@local/hashql/ast/src/node/type/mod.rs index f7d80bfdaea..4a1efef2d91 100644 --- a/libs/@local/hashql/ast/src/node/type/mod.rs +++ b/libs/@local/hashql/ast/src/node/type/mod.rs @@ -80,7 +80,7 @@ pub struct TupleField<'heap> { /// (Float, Float, Float) /// (User, List) /// ``` -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TupleType<'heap> { pub id: NodeId, pub span: SpanId, @@ -110,7 +110,7 @@ pub struct TupleType<'heap> { /// String | Int | Boolean /// Error | Result /// ``` -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UnionType<'heap> { pub id: NodeId, pub span: SpanId, @@ -130,7 +130,7 @@ pub struct UnionType<'heap> { /// Named & Serializable /// Printable & Comparable & Hashable /// ``` -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct IntersectionType<'heap> { pub id: NodeId, pub span: SpanId, @@ -142,7 +142,7 @@ pub struct IntersectionType<'heap> { /// /// This enum represents all possible type constructs in the HashQL language, /// from simple path references to complex composite types. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TypeKind<'heap> { /// The infer type (`_`). /// @@ -199,10 +199,21 @@ pub enum TypeKind<'heap> { /// Represents any type expression in the language, from simple type references /// to complex composite types. Types are used in variable declarations, function /// signatures, generic constraints, and other contexts where type information is needed. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Type<'heap> { pub id: NodeId, pub span: SpanId, pub kind: TypeKind<'heap>, } + +impl Type<'_> { + #[must_use] + pub const fn dummy() -> Self { + Self { + id: NodeId::PLACEHOLDER, + span: SpanId::SYNTHETIC, + kind: TypeKind::Dummy, + } + } +} diff --git a/libs/@local/hashql/ast/src/visit.rs b/libs/@local/hashql/ast/src/visit.rs index 04b38ad1fa1..f5046123035 100644 --- a/libs/@local/hashql/ast/src/visit.rs +++ b/libs/@local/hashql/ast/src/visit.rs @@ -78,7 +78,7 @@ use hashql_core::{span::SpanId, symbol::Ident}; use crate::node::{ expr::{ CallExpr, ClosureExpr, DictExpr, Expr, ExprKind, FieldExpr, IfExpr, IndexExpr, InputExpr, - LetExpr, ListExpr, LiteralExpr, NewTypeExpr, StructExpr, TupleExpr, TypeExpr, UseExpr, + LetExpr, ListExpr, LiteralExpr, NewTypeExpr, StructExpr, TupleExpr, TypeExpr, r#as::AsExpr, call::{Argument, LabeledArgument}, closure::{ClosureParam, ClosureSignature}, @@ -86,7 +86,6 @@ use crate::node::{ list::ListElement, r#struct::StructEntry, tuple::TupleElement, - r#use::{Glob, UseBinding, UseKind}, }, generic::{GenericArgument, GenericConstraint, GenericParam, Generics}, id::NodeId, @@ -250,18 +249,6 @@ pub trait Visitor<'heap> { walk_newtype_expr(self, expr); } - fn visit_use_expr(&mut self, expr: &mut UseExpr<'heap>) { - walk_use_expr(self, expr); - } - - fn visit_use_expr_binding(&mut self, binding: &mut UseBinding<'heap>) { - walk_use_expr_binding(self, binding); - } - - fn visit_use_expr_glob(&mut self, glob: &mut Glob) { - walk_use_expr_glob(self, glob); - } - fn visit_input_expr(&mut self, expr: &mut InputExpr<'heap>) { walk_input_expr(self, expr); } @@ -423,7 +410,6 @@ pub fn walk_expr<'heap, T: Visitor<'heap> + ?Sized>( ExprKind::Let(let_expr) => visitor.visit_let_expr(let_expr), ExprKind::Type(type_expr) => visitor.visit_type_expr(type_expr), ExprKind::NewType(new_type_expr) => visitor.visit_newtype_expr(new_type_expr), - ExprKind::Use(use_expr) => visitor.visit_use_expr(use_expr), ExprKind::Input(input_expr) => visitor.visit_input_expr(input_expr), ExprKind::Closure(closure_expr) => visitor.visit_closure_expr(closure_expr), ExprKind::If(if_expr) => visitor.visit_if_expr(if_expr), @@ -703,60 +689,6 @@ pub fn walk_newtype_expr<'heap, T: Visitor<'heap> + ?Sized>( visitor.visit_expr(body); } -pub fn walk_use_expr<'heap, T: Visitor<'heap> + ?Sized>( - visitor: &mut T, - UseExpr { - id, - span, - path, - kind, - body, - }: &mut UseExpr<'heap>, -) { - visitor.visit_id(id); - visitor.visit_span(span); - - visitor.visit_path(path); - - match kind { - UseKind::Named(bindings) => { - for binding in bindings { - visitor.visit_use_expr_binding(binding); - } - } - UseKind::Glob(glob) => visitor.visit_use_expr_glob(glob), - } - - visitor.visit_expr(body); -} - -pub fn walk_use_expr_binding<'heap, T: Visitor<'heap> + ?Sized>( - visitor: &mut T, - UseBinding { - id, - span, - name, - alias, - }: &mut UseBinding<'heap>, -) { - visitor.visit_id(id); - visitor.visit_span(span); - - visitor.visit_ident(name); - - if let Some(alias) = alias { - visitor.visit_ident(alias); - } -} - -pub fn walk_use_expr_glob<'heap, T: Visitor<'heap> + ?Sized>( - visitor: &mut T, - Glob { id, span }: &mut Glob, -) { - visitor.visit_id(id); - visitor.visit_span(span); -} - pub fn walk_input_expr<'heap, T: Visitor<'heap> + ?Sized>( visitor: &mut T, InputExpr { diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/expander/.spec.toml new file mode 100644 index 00000000000..61945bdb75e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/expander" diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.jsonc new file mode 100644 index 00000000000..bf26c697194 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.jsonc @@ -0,0 +1,8 @@ +//@ run: fail +//@ description: field access with a list expression should error +[ + "::kernel::special_form::access", + { "#literal": "hello" }, + { "#list": [{ "#literal": 1 }] } + //~^ ERROR expected a field name or integer index +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.stderr new file mode 100644 index 00000000000..115d732fea7 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/access-field-list.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-access-field]: Invalid access field + ╭▸ +6 │ { "#list": [{ "#literal": 1 }] } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected a field name or integer index + │ + ╰ help: use a simple identifier like `name` or an integer like `0` for tuple fields \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.jsonc new file mode 100644 index 00000000000..d6cf9e7798e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.jsonc @@ -0,0 +1,8 @@ +//@ run: fail +//@ description: generic arguments on multiple module segments should all be reported +[ + "::kernel::special_form::if", + //~^ ERROR generic argument not allowed here + { "#literal": true }, + { "#literal": 1 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.stderr new file mode 100644 index 00000000000..432c1f1e9fd --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/generics-multiple-module-segments.stderr @@ -0,0 +1,9 @@ +error[expander::generic-arguments-in-module]: Generic arguments in module path + ╭▸ +4 │ "::kernel::special_form::if", + │ ┯━━━━━━ ────── neither is this + │ │ + │ generic argument not allowed here + │ + ├ help: move the generic arguments to the final segment of the path, or remove them + ╰ note: modules are not generic; only the final item in a path can be parameterized \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-absolute-imports.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-absolute-imports.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-absolute-imports.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-absolute-imports.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-absolute-imports.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-absolute-imports.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-absolute-imports.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-absolute-imports.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-glob-import.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-glob-import.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-glob-import.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-glob-import.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-glob-import.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-glob-import.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-glob-import.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-glob-import.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-relative-import.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-relative-import.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-relative-import.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-relative-import.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-relative-import.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-relative-import.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-relative-import.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-relative-import.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-renamed-import.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-renamed-import.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-renamed-import.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-renamed-import.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-renamed-import.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-renamed-import.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/basic-renamed-import.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/basic-renamed-import.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-complex-return-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-complex-return-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-complex-return-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-complex-return-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-complex-return-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-complex-return-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-complex-return-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-complex-return-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-generic-not-resolved.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-generic-not-resolved.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-generic-not-resolved.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-generic-not-resolved.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-generic-not-resolved.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-generic-not-resolved.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-generic-not-resolved.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-generic-not-resolved.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-mixed-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-mixed-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-mixed-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-mixed-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-mixed-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-mixed-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-mixed-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-mixed-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-param-not-resolved.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-param-not-resolved.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-param-not-resolved.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-param-not-resolved.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-param-not-resolved.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-param-not-resolved.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/closure-param-not-resolved.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/closure-param-not-resolved.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-nested-type-parameters.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-nested-type-parameters.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-nested-type-parameters.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-nested-type-parameters.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-nested-type-parameters.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-nested-type-parameters.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-nested-type-parameters.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-nested-type-parameters.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-path-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-path-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-path-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-path-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-path-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-path-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/complex-path-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/complex-path-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.jsonc similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.jsonc index aa31bd58c0f..ac68325d636 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.jsonc @@ -1,9 +1,10 @@ //@ run: pass //@ description: Tests that unresolved nested type variables are handled by dummy replacement, allowing lowering to continue. +//@ suite#continue: true [ "type", "Foo", - //~^ ERROR Cannot find variable 'Bar' + //~^ ERROR cannot find type `Bar` in this scope "Number", "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stderr new file mode 100644 index 00000000000..7071baaaf35 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stderr @@ -0,0 +1,7 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +6 │ "Foo", + │ ━━━ cannot find type `Bar` in this scope + │ + ├ help: check the spelling, or import the name with a `use` statement + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stdout new file mode 100644 index 00000000000..4027d81fecd --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-nested.stdout @@ -0,0 +1,12 @@ +Expr#4294967040@19 + ExprKind (Type) + TypeExpr#4294967040@19 (name: Foo) + GenericConstraint#4294967040@10 (name: T) + Type#4294967040@9 + TypeKind (Path) + Path#4294967040@9 (rooted: false) + PathSegment#4294967040@8 (name: Bar) + Type#4294967040@4294967295 + TypeKind (Dummy) + Expr#4294967040@18 + ExprKind (Underscore) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.jsonc similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.jsonc index 24aebc675f8..e27ca90ac53 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.jsonc @@ -1,9 +1,10 @@ //@ run: pass //@ description: Tests that an unresolved type is handled by dummy replacement, allowing lowering to continue. +//@ suite#continue: true [ "type", "Foo", "T", - //~^ ERROR Cannot find variable 'T' + //~^ ERROR cannot find type `T` in this scope "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stderr new file mode 100644 index 00000000000..319d1b1ea43 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stderr @@ -0,0 +1,28 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +7 │ "T", + │ ━ cannot find type `T` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar local binding exists + ╭╴ +7 - "T", +7 + "Foo", + ╰╴ +help: a similar imported name exists + ╭╴ +7 - "T", +7 + "String", + ├╴ +7 - "T", +7 + "Union", + ├╴ +7 - "T", +7 + "Unknown", + ├╴ +7 - "T", +7 + "Url", + ├╴ +7 - "T", +7 + "|", + ╰╴ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stdout similarity index 82% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stdout index 910dbbe538c..707039108af 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-type.stdout @@ -1,7 +1,7 @@ Expr#4294967040@14 ExprKind (Type) TypeExpr#4294967040@14 (name: Foo) - Type#4294967040@11 + Type#4294967040@4294967295 TypeKind (Dummy) Expr#4294967040@13 ExprKind (Underscore) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.jsonc similarity index 72% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.jsonc index bd32353537f..649f970dae6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.jsonc @@ -1,8 +1,9 @@ //@ run: pass //@ description: Tests that an unresolved value (function `bit_shl`) is handled by dummy replacement, allowing lowering to continue. +//@ suite#continue: true [ "shl", - //~^ ERROR Cannot find variable 'shl' + //~^ ERROR cannot find value `shl` in this scope { "#literal": 1 }, { "#literal": 4 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stderr new file mode 100644 index 00000000000..00628f476fa --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stderr @@ -0,0 +1,11 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +5 │ "shl", + │ ━━━ cannot find value `shl` in this scope + │ + ├ help: bring it into scope: `use ::core::bits::shl in` + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: use one of these fully qualified paths + ╭╴ +5 │ "::core::bits::shl", + ╰╴ ++++++++++++++ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stdout similarity index 93% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stdout index c0320a1ab26..0ecccdd28e0 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/unresolved-value.stdout @@ -1,7 +1,7 @@ Expr#4294967040@8 ExprKind (Call) CallExpr#4294967040@8 - Expr#4294967040@3 + Expr#4294967040@4294967295 ExprKind (Dummy) Argument#4294967040@5 Expr#4294967040@5 diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.jsonc similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.jsonc index 043f5efa8b3..50a218777b3 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.jsonc @@ -1,9 +1,11 @@ //@ run: pass //@ description: Tests that a `use` statement for a non-existent module (`kernel::foo`) is handled by dummy replacement, allowing lowering to continue. +//@ suite#continue: true [ "use", "kernel::foo", - //~^ ERROR Module 'foo' not found + //~^ ERROR cannot find module `foo` "*", ["add", { "#literal": 1 }, { "#literal": 2 }] + //~^ ERROR cannot find value `add` in this scope ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.stderr new file mode 100644 index 00000000000..6cd6c329709 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.stderr @@ -0,0 +1,21 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +9 │ ["add", { "#literal": 1 }, { "#literal": 2 }] + │ ━━━ cannot find value `add` in this scope + │ + ├ help: bring it into scope: `use ::core::math::add in` + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: use one of these fully qualified paths + ╭╴ +9 │ ["::core::math::add", { "#literal": 1 }, { "#literal": 2 }] + ╰╴ ++++++++++++++ + +error[expander::module-not-found]: Module not found + ╭▸ +6 │ "kernel::foo", + │ ┬───── ━━━ cannot find module `foo` in `kernel` + │ │ + │ looked in this module + │ + ├ help: check the module name and ensure it is exported from its parent + ╰ note: only exported sub-modules are reachable via `::` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/continue/use-not-found.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.jsonc index 3bbced724a7..6aece4429b6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Tests that generic arguments in non-final segments of paths are rejected with appropriate error -["::math::add", { "#literal": 2 }, { "#literal": 3 }] -//~^ ERROR Remove this generic argument +["::core::math::add", { "#literal": 2 }, { "#literal": 3 }] +//~^ ERROR generic argument not allowed here diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.stderr new file mode 100644 index 00000000000..b86ada650b8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-absolute-path.stderr @@ -0,0 +1,7 @@ +error[expander::generic-arguments-in-module]: Generic arguments in module path + ╭▸ +3 │ ["::core::math::add", { "#literal": 2 }, { "#literal": 3 }] + │ ━━━━━━━ generic argument not allowed here + │ + ├ help: move the generic arguments to the final segment of the path, or remove them + ╰ note: modules are not generic; only the final item in a path can be parameterized \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.jsonc new file mode 100644 index 00000000000..5220c72d56f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: Tests that generic arguments in 'use' statements are rejected with appropriate error +["use", "kernel::type", { "#tuple": ["Union"] }, "_"] +//~^ ERROR generic arguments are not allowed in `use` paths diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.stderr new file mode 100644 index 00000000000..c7bbe43a8b0 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-argument-use-path.stderr @@ -0,0 +1,6 @@ +error[expander::use-path-generic-arguments]: Generic arguments in use path + ╭▸ +3 │ ["use", "kernel::type", { "#tuple": ["Union"] }, "_"] + │ ━━━━━━━━━━━━━━━━━━━━━ generic arguments are not allowed in `use` paths + │ + ╰ help: remove the generic arguments; `use` imports modules and items, which do not take type parameters at the import site \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-arguments-in-final-segment.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-arguments-in-final-segment.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-arguments-in-final-segment.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-arguments-in-final-segment.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-arguments-in-final-segment.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-arguments-in-final-segment.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-arguments-in-final-segment.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-arguments-in-final-segment.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints.newtype.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints.newtype.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints.newtype.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints.newtype.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints.newtype.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints.newtype.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-constraints.newtype.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/generic-constraints.newtype.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.jsonc similarity index 81% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.jsonc index 8496dae28b8..0d7e82377f3 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Tests that importing from a non-existent module produces an appropriate error with suggestions ["use", "kernel::foo", "*", "_"] -//~^ ERROR Module 'foo' not found +//~^ ERROR cannot find module `foo` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.stderr new file mode 100644 index 00000000000..e0f15939bd1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/glob-not-found.stderr @@ -0,0 +1,9 @@ +error[expander::module-not-found]: Module not found + ╭▸ +3 │ ["use", "kernel::foo", "*", "_"] + │ ┬───── ━━━ cannot find module `foo` in `kernel` + │ │ + │ looked in this module + │ + ├ help: check the module name and ensure it is exported from its parent + ╰ note: only exported sub-modules are reachable via `::` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.jsonc similarity index 74% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.jsonc index c75d6dbff4a..9167b0981b1 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Tests that importing a non-existent item produces an appropriate error with suggestions ["use", "core::bits", { "#tuple": ["lshift"] }, "_"] -//~^ ERROR 'lshift' not found in module 'core::bits' +//~^ ERROR cannot find item `lshift` in module `core::bits` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.stderr new file mode 100644 index 00000000000..632a003cef3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/item-not-found.stderr @@ -0,0 +1,8 @@ +error[expander::item-not-found]: Item not found + ╭▸ +3 │ ["use", "core::bits", { "#tuple": ["lshift"] }, "_"] + │ ┬─── ━━━━━━ cannot find item `lshift` in module `core::bits` + │ │ + │ looked in this module + │ + ╰ help: check the spelling and ensure the item is exported \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-value.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-value.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-value.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-value.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-value.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-value.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-recursive-value.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-recursive-value.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-value.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-value.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-value.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-value.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-value.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-value.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/local-binding-value.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/local-binding-value.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.jsonc similarity index 83% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.jsonc index ec8b77b6c9d..4918886a8b6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Tests that importing from a non-existent module produces an appropriate error with suggestions ["use", "kernel::foo", { "#tuple": ["baz"] }, "_"] -//~^ ERROR Module 'foo' not found +//~^ ERROR cannot find module `foo` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.stderr new file mode 100644 index 00000000000..a6d78041561 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/module-not-found.stderr @@ -0,0 +1,9 @@ +error[expander::module-not-found]: Module not found + ╭▸ +3 │ ["use", "kernel::foo", { "#tuple": ["baz"] }, "_"] + │ ┬───── ━━━ cannot find module `foo` in `kernel` + │ │ + │ looked in this module + │ + ├ help: check the module name and ensure it is exported from its parent + ╰ note: only exported sub-modules are reachable via `::` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/multiple-named-imports.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/multiple-named-imports.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/multiple-named-imports.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/multiple-named-imports.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/multiple-named-imports.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/multiple-named-imports.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/multiple-named-imports.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/multiple-named-imports.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/nested-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/nested-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/nested-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/nested-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/nested-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/nested-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/nested-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/nested-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/newtype-scope.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/newtype-scope.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/newtype-scope.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/newtype-scope.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/newtype-scope.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/newtype-scope.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/newtype-scope.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/newtype-scope.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/path-normalization.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/path-normalization.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/path-normalization.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/path-normalization.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/path-normalization.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/path-normalization.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/path-normalization.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/path-normalization.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/prelude.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/prelude.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/prelude.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/prelude.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/prelude.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/prelude.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/prelude.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/prelude.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/regression-generic-arguments-identifier.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/regression-generic-arguments-identifier.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/regression-generic-arguments-identifier.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/regression-generic-arguments-identifier.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/regression-generic-arguments-identifier.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/regression-generic-arguments-identifier.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/regression-generic-arguments-identifier.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/regression-generic-arguments-identifier.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.jsonc similarity index 81% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.jsonc index 8e684356ab3..090bf0fba61 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.jsonc @@ -5,5 +5,5 @@ { "#literal": true }, ["use", "core::bits", { "#tuple": ["shl"] }, "shl"], "shl" - //~^ ERROR Cannot find variable 'shl' + //~^ ERROR cannot find value `shl` in this scope ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.stderr new file mode 100644 index 00000000000..bf0d7297992 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/rollback.stderr @@ -0,0 +1,11 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +7 │ "shl" + │ ━━━ cannot find value `shl` in this scope + │ + ├ help: bring it into scope: `use ::core::bits::shl in` + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: use one of these fully qualified paths + ╭╴ +7 │ "::core::bits::shl" + ╰╴ ++++++++++++++ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-imports.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-imports.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-imports.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-imports.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-imports.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-imports.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-imports.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-imports.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-prelude.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-prelude.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-prelude.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-prelude.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-prelude.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-prelude.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/shadowed-prelude.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/shadowed-prelude.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/substitution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/substitution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/substitution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/substitution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/substitution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/substitution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/substitution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/substitution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/universe-ambiguity-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/universe-ambiguity-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/universe-ambiguity-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/universe-ambiguity-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/universe-ambiguity-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/universe-ambiguity-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/universe-ambiguity-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/universe-ambiguity-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.jsonc similarity index 80% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.jsonc index f4d7db66077..21758d075c5 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.jsonc @@ -9,6 +9,6 @@ "foo", { "#literal": 42 }, "föo" - //~^ ERROR Cannot find variable 'föo' + //~^ ERROR cannot find value `föo` in this scope ] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.stderr new file mode 100644 index 00000000000..a746ea4e5f0 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/unresolver-variable.stderr @@ -0,0 +1,11 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +11 │ "föo" + │ ━━━ cannot find value `föo` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar local binding exists + ╭╴ +11 - "föo" +11 + "foo" + ╰╴ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-let.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-let.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-let.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-let.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-let.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-let.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-let.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-let.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-use.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-use.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-use.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-use.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-use.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-use.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/import-resolver/use-shadowing-use.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/import-resolver/use-shadowing-use.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.jsonc new file mode 100644 index 00000000000..5200a4d7f25 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: input binding name must be a simple identifier +["::kernel::special_form::input", { "#literal": 1 }, "Integer"] +//~^ ERROR expected a simple identifier for the `input` name diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.stderr new file mode 100644 index 00000000000..2903b3cbc85 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/input-invalid-name.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +3 │ ["::kernel::special_form::input", { "#literal": 1 }, "Integer"] + │ ━━━━━━━━━━━━━━━━━ expected a simple identifier for the `input` name + │ + ├ help: write `(input name type)` with a plain name such as `user_id` + ╰ note: the first argument to `input` declares a named parameter and must be a plain identifier \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.jsonc new file mode 100644 index 00000000000..22bebe3f7df --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: input binding name must be a simple identifier, not a qualified path +["::kernel::special_form::input", "x::y", "Integer"] +//~^ ERROR expected a simple identifier for the `input` name diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.stderr new file mode 100644 index 00000000000..862e0bd8425 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/input-name-is-path.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +3 │ ["::kernel::special_form::input", "x::y", "Integer"] + │ ━━━━ expected a simple identifier for the `input` name + │ + ├ help: write `(input name type)` with a plain name such as `user_id` + ╰ note: the first argument to `input` declares a named parameter and must be a plain identifier \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.jsonc new file mode 100644 index 00000000000..f779863b7fa --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: generic arguments on intrinsic special forms should error +[ + "::kernel::special_form::if", + //~^ ERROR `if` does not accept generic arguments + { "#literal": true }, + { "#literal": 1 }, + { "#literal": 2 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.stderr new file mode 100644 index 00000000000..74c28df8532 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-generic-arguments.stderr @@ -0,0 +1,6 @@ +error[expander::intrinsic-generic-arguments]: Generic arguments on intrinsic + ╭▸ +4 │ "::kernel::special_form::if", + │ ━━━━━━━━━━━ `if` does not accept generic arguments + │ + ╰ help: remove the generic arguments from `if` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.jsonc new file mode 100644 index 00000000000..2975105a126 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: generic arguments on type intrinsics (Union/Intersection) should error +[ + "::kernel::special_form::type", + "Foo", + ["::kernel::type::Union", "String"], + //~^ ERROR `Union` does not accept generic arguments + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.stderr new file mode 100644 index 00000000000..380aecd05b9 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/intrinsic-type-generic-arguments.stderr @@ -0,0 +1,6 @@ +error[expander::intrinsic-generic-arguments]: Generic arguments on intrinsic + ╭▸ +6 │ ["::kernel::type::Union", "String"], + │ ━━━━━━━━━━━━━━ `Union` does not accept generic arguments + │ + ╰ help: remove the generic arguments from `Union` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.jsonc new file mode 100644 index 00000000000..3da524bcc91 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `access` +[ + "::kernel::special_form::access", + { "#literal": "hello" }, + "name", + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `.` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.stderr new file mode 100644 index 00000000000..dbace318ec7 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-access.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +7 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `.` + │ + ╰ help: pass the arguments positionally: `(. value field)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.jsonc new file mode 100644 index 00000000000..2c3ea60ee1f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `as` +[ + "::kernel::special_form::as", + { "#literal": 1 }, + "Integer", + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `as` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.stderr new file mode 100644 index 00000000000..e91b4487074 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-as.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +7 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `as` + │ + ╰ help: pass the arguments positionally: `(as value type)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.jsonc new file mode 100644 index 00000000000..a7219d96406 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.jsonc @@ -0,0 +1,13 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `fn` +[ + "::kernel::special_form::fn", + { "#tuple": ["T"] }, + { "#struct": { "x": "Integer" } }, + "Integer", + { "#literal": 0 }, + { + ":extra": { "#literal": 0 } + //~^ ERROR labeled arguments are not allowed in `fn` + } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.stderr new file mode 100644 index 00000000000..951bbac9655 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-fn.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +10 │ ":extra": { "#literal": 0 } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `fn` + │ + ╰ help: pass the arguments positionally: `(fn generics params return-type body)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.jsonc new file mode 100644 index 00000000000..3d6303f0327 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `[]` +[ + "::kernel::special_form::index", + { "#literal": "hello" }, + { "#literal": 0 }, + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `[]` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.stderr new file mode 100644 index 00000000000..cb1dd2a315a --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-index.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +7 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `[]` + │ + ╰ help: pass the arguments positionally: `([] collection index)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.jsonc new file mode 100644 index 00000000000..8cd0638f2e0 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `input` +[ + "::kernel::special_form::input", + "x", + "Integer", + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `input` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.stderr new file mode 100644 index 00000000000..5c67c19b253 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-input.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +7 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `input` + │ + ╰ help: pass the arguments positionally: `(input name type)` or `(input name type default)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.jsonc new file mode 100644 index 00000000000..f236487d7f1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.jsonc @@ -0,0 +1,10 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `let` +[ + "::kernel::special_form::let", + "x", + { "#literal": 1 }, + "x", + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `let` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.stderr new file mode 100644 index 00000000000..9fd9862fa12 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-let.stderr @@ -0,0 +1,7 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +8 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `let` + │ + ├ help: pass the arguments positionally: `(let name value body)` or `(let name type value body)` + ╰ note: `let` has a fixed argument order: name, optional type annotation, value, body \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.jsonc new file mode 100644 index 00000000000..e87be19e841 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.jsonc @@ -0,0 +1,10 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `newtype` +[ + "::kernel::special_form::newtype", + "Foo", + "Integer", + "Foo", + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `newtype` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.stderr new file mode 100644 index 00000000000..4aa765f1fd8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-newtype.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +8 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `newtype` + │ + ╰ help: pass the arguments positionally: `(newtype Name type-expr body)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.jsonc new file mode 100644 index 00000000000..1f03db4a6bf --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.jsonc @@ -0,0 +1,10 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `type` +[ + "::kernel::special_form::type", + "Foo", + "Integer", + { "#literal": 0 }, + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `type` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.stderr new file mode 100644 index 00000000000..d5456216f5b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-type.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +8 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `type` + │ + ╰ help: pass the arguments positionally: `(type Name type-expr body)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.jsonc new file mode 100644 index 00000000000..79c4ee5da55 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.jsonc @@ -0,0 +1,10 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in `use` +[ + "::kernel::special_form::use", + "::core::math", + { "#tuple": ["add"] }, + { "#literal": 0 }, + { ":extra": { "#literal": 0 } } + //~^ ERROR labeled arguments are not allowed in `use` +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.stderr new file mode 100644 index 00000000000..3da726aa99f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/labeled-argument-use.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +8 │ { ":extra": { "#literal": 0 } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `use` + │ + ╰ help: pass the arguments positionally: `(use path imports body)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.jsonc new file mode 100644 index 00000000000..83cbd93c4ad --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: rebinding an intrinsic special form should be swallowed (body returned directly) +[ + "::kernel::special_form::let", + "my_if", + "::kernel::special_form::if", + ["my_if", { "#literal": true }, { "#literal": 1 }, { "#literal": 2 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.stdout new file mode 100644 index 00000000000..867f42c0938 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-swallow.stdout @@ -0,0 +1,17 @@ +Expr#4294967040@30 + ExprKind (If) + IfExpr#4294967040@30 + Expr#4294967040@25 + ExprKind (Literal) + LiteralExpr#4294967040@24 + Primitive (True) + Expr#4294967040@27 + ExprKind (Literal) + LiteralExpr#4294967040@26 + Primitive (Integer) + Integer (1) + Expr#4294967040@29 + ExprKind (Literal) + LiteralExpr#4294967040@28 + Primitive (Integer) + Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.jsonc new file mode 100644 index 00000000000..d0984ac708c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.jsonc @@ -0,0 +1,10 @@ +//@ run: fail +//@ description: type annotation on a rebinding of an intrinsic should produce a diagnostic +[ + "::kernel::special_form::let", + "my_if", + "Integer", + //~^ ERROR type annotation on `my_if` is not allowed here + "::kernel::special_form::if", + ["my_if", { "#literal": true }, { "#literal": 1 }, { "#literal": 2 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.stderr new file mode 100644 index 00000000000..9255a2b760f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/let-intrinsic-type-annotation.stderr @@ -0,0 +1,10 @@ +error[expander::intrinsic-type-annotation]: Type annotation on intrinsic binding + ╭▸ +6 │ "Integer", + │ ━━━━━━━ type annotation on `my_if` is not allowed here +7 │ //~^ ERROR type annotation on `my_if` is not allowed here +8 │ "::kernel::special_form::if", + │ ────────────────────────── this resolves to a compiler intrinsic + │ + ├ help: remove the type annotation: `(let my_if value body)` + ╰ note: compiler intrinsics cannot be given a type annotation because they are not ordinary values \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.jsonc new file mode 100644 index 00000000000..9be08060aa1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: referencing a nonexistent root package should error +"::nonexistent::something" +//~^ ERROR cannot find package `nonexistent` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.stderr new file mode 100644 index 00000000000..0f3d2b2ac69 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/package-not-found.stderr @@ -0,0 +1,10 @@ +error[expander::package-not-found]: Package not found + ╭▸ +3 │ "::nonexistent::something" + │ ┬─┯━━━━━━━━━━─────────── + │ │ │ + │ │ cannot find package `nonexistent` + │ in this path + │ + ├ help: check the package name, or add it to the project dependencies + ╰ note: absolute paths start from an installed package \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.jsonc similarity index 55% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.jsonc index 98df100866f..067e36bbd13 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: test that `let` does not leak out of scope -["::+", { "#literal": 3 }, { "#literal": 4 }] +["::core::math::add", { "#literal": 3 }, { "#literal": 4 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.stdout new file mode 100644 index 00000000000..3d7d0fa8d95 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/absolute-path.stdout @@ -0,0 +1,21 @@ +Expr#4294967040@12 + ExprKind (Call) + CallExpr#4294967040@12 + Expr#4294967040@7 + ExprKind (Path) + Path#4294967040@7 (rooted: true) + PathSegment#4294967040@2 (name: core) + PathSegment#4294967040@4 (name: math) + PathSegment#4294967040@6 (name: add) + Argument#4294967040@9 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (3) + Argument#4294967040@11 + Expr#4294967040@11 + ExprKind (Literal) + LiteralExpr#4294967040@10 + Primitive (Integer) + Integer (4) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-absolute-path.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-absolute-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-absolute-path.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-absolute-path.stdout new file mode 100644 index 00000000000..dd8729ec5b1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-absolute-path.stdout @@ -0,0 +1,30 @@ +Expr#4294967040@25 + ExprKind (Let) + LetExpr#4294967040@25 (name: a) + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@10 (name: core) + PathSegment#4294967040@12 (name: math) + PathSegment#4294967040@14 (name: add) + Expr#4294967040@24 + ExprKind (Call) + CallExpr#4294967040@24 + Expr#4294967040@19 + ExprKind (Path) + Path#4294967040@19 (rooted: true) + PathSegment#4294967040@18 (name: core) + PathSegment#4294967040@18 (name: math) + PathSegment#4294967040@18 (name: add) + Argument#4294967040@21 + Expr#4294967040@21 + ExprKind (Literal) + LiteralExpr#4294967040@20 + Primitive (Integer) + Integer (1) + Argument#4294967040@23 + Expr#4294967040@23 + ExprKind (Literal) + LiteralExpr#4294967040@22 + Primitive (Integer) + Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.jsonc similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.jsonc index 31959b5ee3e..ba9cc5d88a5 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.jsonc @@ -2,5 +2,5 @@ //@ description: Should be able to alias to a symbol and from a symbol // prettier-ignore ["let", "!&&", "&&", - ["!&&", "a", "b"] + ["!&&", {"#literal": true}, {"#literal": false}] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.stdout new file mode 100644 index 00000000000..ec21b0e3aec --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/alias-symbol.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@21 + ExprKind (Let) + LetExpr#4294967040@21 (name: !&&) + Expr#4294967040@11 + ExprKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: core) + PathSegment#4294967040@10 (name: bool) + PathSegment#4294967040@10 (name: and) + Expr#4294967040@20 + ExprKind (Call) + CallExpr#4294967040@20 + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: core) + PathSegment#4294967040@14 (name: bool) + PathSegment#4294967040@14 (name: and) + Argument#4294967040@17 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (True) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (False) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.jsonc new file mode 100644 index 00000000000..1e6124681c8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: Test let expression with non-path target is not treated specially +[ + "let", + ["+", { "#literal": 1 }, { "#literal": 2 }], + //~^ ERROR expected a simple identifier for the `let` binding name + { "#literal": 3 }, + { "#literal": 4 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.stderr new file mode 100644 index 00000000000..91bcf496a96 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/invalid-let-expr.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +5 │ ["+", { "#literal": 1 }, { "#literal": 2 }], + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.jsonc similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.jsonc index d75ffe3c11c..1bd76f1783f 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: if given that `a = b`, then `a` should be replaced with `b` in the body -["let", "a", "b", ["a", "x", "x"]] +["let", "b", { "#literal": 0 }, ["let", "a", "b", "a"]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.stdout new file mode 100644 index 00000000000..b5a357edeb3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3-alias.stdout @@ -0,0 +1,19 @@ +Expr#4294967040@27 + ExprKind (Let) + LetExpr#4294967040@27 (name: b) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (0) + Expr#4294967040@26 + ExprKind (Let) + LetExpr#4294967040@26 (name: a) + Expr#4294967040@21 + ExprKind (Path) + Path#4294967040@21 (rooted: false) + PathSegment#4294967040@20 (name: b) + Expr#4294967040@25 + ExprKind (Path) + Path#4294967040@25 (rooted: false) + PathSegment#4294967040@24 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.jsonc similarity index 54% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.jsonc index 51dc74b2e6b..40ae9e7d119 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that `let/3` is resolved to `::kernel::special_form::let` -["let", "a", "b", ["+", { "#literal": 2 }, { "#literal": 3 }]] +["let", "a", { "#literal": 1 }, ["+", { "#literal": 2 }, { "#literal": 3 }]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.stdout new file mode 100644 index 00000000000..a1648a61598 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-3.stdout @@ -0,0 +1,29 @@ +Expr#4294967040@19 + ExprKind (Let) + LetExpr#4294967040@19 (name: a) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) + Expr#4294967040@18 + ExprKind (Call) + CallExpr#4294967040@18 + Expr#4294967040@13 + ExprKind (Path) + Path#4294967040@13 (rooted: true) + PathSegment#4294967040@12 (name: core) + PathSegment#4294967040@12 (name: math) + PathSegment#4294967040@12 (name: add) + Argument#4294967040@15 + Expr#4294967040@15 + ExprKind (Literal) + LiteralExpr#4294967040@14 + Primitive (Integer) + Integer (2) + Argument#4294967040@17 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.jsonc similarity index 50% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.jsonc index 8f85f296dda..e122984c405 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.jsonc @@ -1,3 +1,9 @@ //@ run: pass //@ description: if given that `a = b`, then `a` should be replaced with `b` in the body -["let", "a", "Int", "b", ["a", "x", "x"]] +[ + "let", + "b", + "Integer", + { "#literal": 2 }, + ["let", "a", "Integer", "b", ["+", "a", "a"]] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.stdout new file mode 100644 index 00000000000..5126c0a7645 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4-alias.stdout @@ -0,0 +1,46 @@ +Expr#4294967040@44 + ExprKind (Let) + LetExpr#4294967040@44 (name: b) + Expr#4294967040@13 + ExprKind (Literal) + LiteralExpr#4294967040@12 + Primitive (Integer) + Integer (2) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@43 + ExprKind (Let) + LetExpr#4294967040@43 (name: a) + Expr#4294967040@29 + ExprKind (Path) + Path#4294967040@29 (rooted: false) + PathSegment#4294967040@28 (name: b) + Type#4294967040@25 + TypeKind (Path) + Path#4294967040@25 (rooted: true) + PathSegment#4294967040@24 (name: kernel) + PathSegment#4294967040@24 (name: type) + PathSegment#4294967040@24 (name: Integer) + Expr#4294967040@42 + ExprKind (Call) + CallExpr#4294967040@42 + Expr#4294967040@33 + ExprKind (Path) + Path#4294967040@33 (rooted: true) + PathSegment#4294967040@32 (name: core) + PathSegment#4294967040@32 (name: math) + PathSegment#4294967040@32 (name: add) + Argument#4294967040@37 + Expr#4294967040@37 + ExprKind (Path) + Path#4294967040@37 (rooted: false) + PathSegment#4294967040@36 (name: a) + Argument#4294967040@41 + Expr#4294967040@41 + ExprKind (Path) + Path#4294967040@41 (rooted: false) + PathSegment#4294967040@40 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.jsonc new file mode 100644 index 00000000000..fe9e415afa7 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.jsonc @@ -0,0 +1,9 @@ +//@ run: pass +//@ description: Test that `let/4` is resolved to `::kernel::special_form::let` +[ + "let", + "a", + "Integer", + { "#literal": 0 }, + ["+", { "#literal": 2 }, { "#literal": 3 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.stdout new file mode 100644 index 00000000000..8714b9e097a --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-4.stdout @@ -0,0 +1,35 @@ +Expr#4294967040@23 + ExprKind (Let) + LetExpr#4294967040@23 (name: a) + Expr#4294967040@13 + ExprKind (Literal) + LiteralExpr#4294967040@12 + Primitive (Integer) + Integer (0) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@22 + ExprKind (Call) + CallExpr#4294967040@22 + Expr#4294967040@17 + ExprKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: core) + PathSegment#4294967040@16 (name: math) + PathSegment#4294967040@16 (name: add) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (2) + Argument#4294967040@21 + Expr#4294967040@21 + ExprKind (Literal) + LiteralExpr#4294967040@20 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.jsonc similarity index 63% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.jsonc index 0aeacf7011c..e1bccc73874 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: let without expansion in it's already defined form should do replacement -["::kernel::special_form::let", "a", "b", ["a", "x", "x"]] +["::kernel::special_form::let", "a", { "#literal": 1 }, "a"] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.stdout new file mode 100644 index 00000000000..30698b48caf --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-absolute.stdout @@ -0,0 +1,12 @@ +Expr#4294967040@18 + ExprKind (Let) + LetExpr#4294967040@18 (name: a) + Expr#4294967040@13 + ExprKind (Literal) + LiteralExpr#4294967040@12 + Primitive (Integer) + Integer (1) + Expr#4294967040@17 + ExprKind (Path) + Path#4294967040@17 (rooted: false) + PathSegment#4294967040@16 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.jsonc new file mode 100644 index 00000000000..0a0cb4b74c9 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: if the first argument cannot be interpreted as an ident, do nothing +["let", "a", { "#literal": 0 }, { "#literal": 0 }] +//~^ ERROR expected a simple identifier for the `let` binding name diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.stderr new file mode 100644 index 00000000000..02029e90c7d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/let-ident-generic.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +3 │ ["let", "a", { "#literal": 0 }, { "#literal": 0 }] + │ ━━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.jsonc similarity index 77% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.jsonc index 9e0f982f6fe..0fe51807741 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test resolution of multi-segment paths -["+::total"] +["core::math::add"] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.stdout new file mode 100644 index 00000000000..9c91c17718b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/multi-segment-resolution.stdout @@ -0,0 +1,9 @@ +Expr#4294967040@8 + ExprKind (Call) + CallExpr#4294967040@8 + Expr#4294967040@7 + ExprKind (Path) + Path#4294967040@7 (rooted: true) + PathSegment#4294967040@2 (name: core) + PathSegment#4294967040@4 (name: math) + PathSegment#4294967040@6 (name: add) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.jsonc index d5be628419a..9acb0b84613 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.jsonc @@ -1,8 +1,8 @@ //@ run: pass //@ description: Test nested let expressions with proper scoping // prettier-ignore -["let", "a", "c", -["let", "b", "d", -["let", "a", "e", - ["a", ["b", {"#literal": 2}]] +["let", "a", {"#literal": 1}, +["let", "b", {"#literal": 2}, +["let", "a", {"#literal": 3}, + "a" ]]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.stdout new file mode 100644 index 00000000000..2bdb03c279c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/nested-let.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@36 + ExprKind (Let) + LetExpr#4294967040@36 (name: a) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) + Expr#4294967040@35 + ExprKind (Let) + LetExpr#4294967040@35 (name: b) + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (2) + Expr#4294967040@34 + ExprKind (Let) + LetExpr#4294967040@34 (name: a) + Expr#4294967040@29 + ExprKind (Literal) + LiteralExpr#4294967040@28 + Primitive (Integer) + Integer (3) + Expr#4294967040@33 + ExprKind (Path) + Path#4294967040@33 (rooted: false) + PathSegment#4294967040@32 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.jsonc new file mode 100644 index 00000000000..8104fc8862b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: if given that `a = b`, then `a` should NOT be replaced with `b` in the body +[ + "newtype", + "b", + "Integer", + ["newtype", "a", "b", ["a", { "#literal": 1 }, { "#literal": 2 }]] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.stdout new file mode 100644 index 00000000000..ad312ab988e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3-alias.stdout @@ -0,0 +1,35 @@ +Expr#4294967040@34 + ExprKind (NewType) + NewTypeExpr#4294967040@34 (name: b) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@33 + ExprKind (NewType) + NewTypeExpr#4294967040@33 (name: a) + Type#4294967040@23 + TypeKind (Path) + Path#4294967040@23 (rooted: false) + PathSegment#4294967040@22 (name: b) + Expr#4294967040@32 + ExprKind (Call) + CallExpr#4294967040@32 + Expr#4294967040@27 + ExprKind (Path) + Path#4294967040@27 (rooted: false) + PathSegment#4294967040@26 (name: a) + Argument#4294967040@29 + Expr#4294967040@29 + ExprKind (Literal) + LiteralExpr#4294967040@28 + Primitive (Integer) + Integer (1) + Argument#4294967040@31 + Expr#4294967040@31 + ExprKind (Literal) + LiteralExpr#4294967040@30 + Primitive (Integer) + Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.jsonc index 2ba8e66aadf..3b8836029de 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that `newtype/3` is resolved to `::kernel::special_form::type` -["newtype", "a", "b", ["+", { "#literal": 2 }, { "#literal": 3 }]] +["newtype", "a", "Integer", ["+", { "#literal": 2 }, { "#literal": 3 }]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.stdout new file mode 100644 index 00000000000..3337363df3d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/newtype-3.stdout @@ -0,0 +1,30 @@ +Expr#4294967040@21 + ExprKind (NewType) + NewTypeExpr#4294967040@21 (name: a) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@20 + ExprKind (Call) + CallExpr#4294967040@20 + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: core) + PathSegment#4294967040@14 (name: math) + PathSegment#4294967040@14 (name: add) + Argument#4294967040@17 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (2) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc similarity index 61% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc index 0fa918d61d5..adb937582f1 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.jsonc @@ -3,11 +3,6 @@ [ "if", { "#literal": true }, - [ - "let", - "let", - ["+", { "#literal": 1 }, { "#literal": 2 }], - ["let", "a", "b"] - ], - ["let", "a", "b"] + ["let", "let", "+", ["let", { "#literal": 1 }, { "#literal": 2 }]], + ["let", "a", { "#literal": 0 }, "a"] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout new file mode 100644 index 00000000000..529dd5cbb13 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout @@ -0,0 +1,49 @@ +Expr#4294967040@43 + ExprKind (If) + IfExpr#4294967040@43 + Expr#4294967040@5 + ExprKind (Literal) + LiteralExpr#4294967040@4 + Primitive (True) + Expr#4294967040@27 + ExprKind (Let) + LetExpr#4294967040@27 (name: let) + Expr#4294967040@17 + ExprKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: core) + PathSegment#4294967040@16 (name: math) + PathSegment#4294967040@16 (name: add) + Expr#4294967040@26 + ExprKind (Call) + CallExpr#4294967040@26 + Expr#4294967040@21 + ExprKind (Path) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: core) + PathSegment#4294967040@20 (name: math) + PathSegment#4294967040@20 (name: add) + Argument#4294967040@23 + Expr#4294967040@23 + ExprKind (Literal) + LiteralExpr#4294967040@22 + Primitive (Integer) + Integer (1) + Argument#4294967040@25 + Expr#4294967040@25 + ExprKind (Literal) + LiteralExpr#4294967040@24 + Primitive (Integer) + Integer (2) + Expr#4294967040@42 + ExprKind (Let) + LetExpr#4294967040@42 (name: a) + Expr#4294967040@37 + ExprKind (Literal) + LiteralExpr#4294967040@36 + Primitive (Integer) + Integer (0) + Expr#4294967040@41 + ExprKind (Path) + Path#4294967040@41 (rooted: false) + PathSegment#4294967040@40 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.jsonc similarity index 58% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.jsonc index 20cb42ec175..99590557412 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: This is funky, re-assign `let` to a new value, so that it is no longer resolved -["let", "let", ["+", { "#literal": 1 }, { "#literal": 2 }], ["let", "a", "b"]] +["let", "let", ["+", { "#literal": 1 }, { "#literal": 2 }], "let"] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.stdout new file mode 100644 index 00000000000..6cc6a163c54 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign-prelude.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@21 + ExprKind (Let) + LetExpr#4294967040@21 (name: let) + Expr#4294967040@16 + ExprKind (Call) + CallExpr#4294967040@16 + Expr#4294967040@11 + ExprKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: core) + PathSegment#4294967040@10 (name: math) + PathSegment#4294967040@10 (name: add) + Argument#4294967040@13 + Expr#4294967040@13 + ExprKind (Literal) + LiteralExpr#4294967040@12 + Primitive (Integer) + Integer (1) + Argument#4294967040@15 + Expr#4294967040@15 + ExprKind (Literal) + LiteralExpr#4294967040@14 + Primitive (Integer) + Integer (2) + Expr#4294967040@20 + ExprKind (Path) + Path#4294967040@20 (rooted: false) + PathSegment#4294967040@19 (name: let) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.jsonc similarity index 70% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.jsonc index f82614ebffd..be00d4415ab 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.jsonc @@ -1,8 +1,8 @@ //@ run: pass //@ description: Given b = c; a = b; `a` should be replaced as `d` // prettier-ignore -["let", "c", "d", +["let", "c", {"#literal": 1}, ["let", "b", "c", ["let", "a", "b", - ["a", {"#literal": 1}, {"#literal": 3}] + ["+", "a", {"#literal": 3}] ]]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.stdout new file mode 100644 index 00000000000..8af742d8902 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/re-assign.stdout @@ -0,0 +1,42 @@ +Expr#4294967040@47 + ExprKind (Let) + LetExpr#4294967040@47 (name: c) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) + Expr#4294967040@46 + ExprKind (Let) + LetExpr#4294967040@46 (name: b) + Expr#4294967040@21 + ExprKind (Path) + Path#4294967040@21 (rooted: false) + PathSegment#4294967040@20 (name: c) + Expr#4294967040@45 + ExprKind (Let) + LetExpr#4294967040@45 (name: a) + Expr#4294967040@33 + ExprKind (Path) + Path#4294967040@33 (rooted: false) + PathSegment#4294967040@32 (name: b) + Expr#4294967040@44 + ExprKind (Call) + CallExpr#4294967040@44 + Expr#4294967040@37 + ExprKind (Path) + Path#4294967040@37 (rooted: true) + PathSegment#4294967040@36 (name: core) + PathSegment#4294967040@36 (name: math) + PathSegment#4294967040@36 (name: add) + Argument#4294967040@41 + Expr#4294967040@41 + ExprKind (Path) + Path#4294967040@41 (rooted: false) + PathSegment#4294967040@40 (name: a) + Argument#4294967040@43 + Expr#4294967040@43 + ExprKind (Literal) + LiteralExpr#4294967040@42 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.jsonc similarity index 82% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.jsonc index 93791049032..f0e648d9a24 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that generic arguments are resolved correctly -["`+`<`-`>"] +["`+`"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.stdout similarity index 73% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.stdout index cce3d843ef4..7b5c4d0028c 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-generics.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-generics.stdout @@ -12,6 +12,6 @@ Expr#4294967040@9 Type#4294967040@5 TypeKind (Path) Path#4294967040@5 (rooted: true) - PathSegment#4294967040@4 (name: core) - PathSegment#4294967040@4 (name: math) - PathSegment#4294967040@4 (name: sub) + PathSegment#4294967040@4 (name: kernel) + PathSegment#4294967040@4 (name: type) + PathSegment#4294967040@4 (name: Integer) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.jsonc similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.jsonc index d3ee7b0874c..b6eef3a3518 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that symbols are resolved to their corresponding mapping -["&&", "a", "b"] +["&&", { "#literal": true }, { "#literal": false }] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.stdout similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.stdout index 545460704e6..f403eb0889b 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/absolute-path.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/resolve-symbol.stdout @@ -4,16 +4,16 @@ Expr#4294967040@8 Expr#4294967040@3 ExprKind (Path) Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: +) + PathSegment#4294967040@2 (name: core) + PathSegment#4294967040@2 (name: bool) + PathSegment#4294967040@2 (name: and) Argument#4294967040@5 Expr#4294967040@5 ExprKind (Literal) LiteralExpr#4294967040@4 - Primitive (Integer) - Integer (3) + Primitive (True) Argument#4294967040@7 Expr#4294967040@7 ExprKind (Literal) LiteralExpr#4294967040@6 - Primitive (Integer) - Integer (4) + Primitive (False) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.jsonc new file mode 100644 index 00000000000..f8631411a08 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.jsonc @@ -0,0 +1,13 @@ +//@ run: pass +//@ description: Test bindings are restored after let expressions +// prettier-ignore +["let", "outer", {"#literal": 0}, + ["let", "x", {"#literal": 0}, + ["if", {"#literal": true}, + ["let", "x", "outer", + "x" + ], + "x" + ] + ] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.stdout new file mode 100644 index 00000000000..4967a335929 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/restoration-bindings.stdout @@ -0,0 +1,38 @@ +Expr#4294967040@49 + ExprKind (Let) + LetExpr#4294967040@49 (name: outer) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (0) + Expr#4294967040@48 + ExprKind (Let) + LetExpr#4294967040@48 (name: x) + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (0) + Expr#4294967040@47 + ExprKind (If) + IfExpr#4294967040@47 + Expr#4294967040@25 + ExprKind (Literal) + LiteralExpr#4294967040@24 + Primitive (True) + Expr#4294967040@42 + ExprKind (Let) + LetExpr#4294967040@42 (name: x) + Expr#4294967040@37 + ExprKind (Path) + Path#4294967040@37 (rooted: false) + PathSegment#4294967040@36 (name: outer) + Expr#4294967040@41 + ExprKind (Path) + Path#4294967040@41 (rooted: false) + PathSegment#4294967040@40 (name: x) + Expr#4294967040@46 + ExprKind (Path) + Path#4294967040@46 (rooted: false) + PathSegment#4294967040@45 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.jsonc index e5c9cc86f7e..a9295fbdea7 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: if given that `a = b`, then `a` should be replaced with `b` in the body -["type", "a", "b", ["a", "x", "x"]] +["type", "b", "Integer", ["type", "a", "b", ["as", { "#literal": 0 }, "a"]]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.stdout new file mode 100644 index 00000000000..7971dcf25cc --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3-alias.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@36 + ExprKind (Type) + TypeExpr#4294967040@36 (name: b) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@35 + ExprKind (Type) + TypeExpr#4294967040@35 (name: a) + Type#4294967040@23 + TypeKind (Path) + Path#4294967040@23 (rooted: false) + PathSegment#4294967040@22 (name: b) + Expr#4294967040@34 + ExprKind (As) + AsExpr#4294967040@34 + Expr#4294967040@29 + ExprKind (Literal) + LiteralExpr#4294967040@28 + Primitive (Integer) + Integer (0) + Type#4294967040@33 + TypeKind (Path) + Path#4294967040@33 (rooted: false) + PathSegment#4294967040@32 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.jsonc index 21c2cba1d15..f518a27b03e 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that `type/3` is resolved to `::kernel::special_form::type` -["type", "a", "b", ["+", { "#literal": 2 }, { "#literal": 3 }]] +["type", "a", "Integer", ["+", { "#literal": 2 }, { "#literal": 3 }]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.stdout new file mode 100644 index 00000000000..6b27ddc6d62 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-3.stdout @@ -0,0 +1,30 @@ +Expr#4294967040@21 + ExprKind (Type) + TypeExpr#4294967040@21 (name: a) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@20 + ExprKind (Call) + CallExpr#4294967040@20 + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: core) + PathSegment#4294967040@14 (name: math) + PathSegment#4294967040@14 (name: add) + Argument#4294967040@17 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (2) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.jsonc similarity index 65% rename from libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.jsonc index 62ffcdf7927..b74987f3b5b 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Test that `type/4` is resolved to `::kernel::special_form::type`, but that no aliases are created. -["type", "a", "Int", "b", ["+", "a", "c"]] +["type", "a", "Integer", ["+", { "#literal": 2 }, { "#literal": 3 }]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.stdout new file mode 100644 index 00000000000..6b27ddc6d62 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/pre-expansion-name-resolver/type-4.stdout @@ -0,0 +1,30 @@ +Expr#4294967040@21 + ExprKind (Type) + TypeExpr#4294967040@21 (name: a) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@20 + ExprKind (Call) + CallExpr#4294967040@20 + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: core) + PathSegment#4294967040@14 (name: math) + PathSegment#4294967040@14 (name: add) + Argument#4294967040@17 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (2) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.jsonc similarity index 66% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.jsonc index f6cc8513ce4..1c3013eed75 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: access/1 should error out ["::kernel::special_form::access", "x"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 2 arguments to `.`, found 1 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.stderr new file mode 100644 index 00000000000..929d2674f9c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-1.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::access", "x"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 2 arguments to `.`, found 1 + │ + ├ help: use `(. value field)` + ╰ note: the first argument is the value and the second is the field name or index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.jsonc new file mode 100644 index 00000000000..fd9e4454206 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: access/2 should compile +["::kernel::special_form::access", { "#literal": 1 }, { "#literal": 2 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.stdout new file mode 100644 index 00000000000..ca3c7e99213 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-2.stdout @@ -0,0 +1,8 @@ +Expr#4294967040@12 + ExprKind (Field) + FieldExpr#4294967040@12 (field: 2) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.jsonc similarity index 77% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.jsonc index 1d33c5afb63..5934d367ee7 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.jsonc @@ -5,5 +5,5 @@ "x", "y", "z" - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.stderr new file mode 100644 index 00000000000..33589f2dce1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-3.stderr @@ -0,0 +1,14 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ┏ [ +4 │ ┃ "::kernel::special_form::access", +5 │ ┃ "x", +6 │ ┃ "y", +7 │ ┃ "z" + │ ┃ ─ unexpected argument +8 │ ┃ //~^ ERROR unexpected argument +9 │ ┃ ] + │ ┗━┛ expected 2 arguments to `.`, found 3 + │ + ├ help: use `(. value field)` + ╰ note: the first argument is the value and the second is the field name or index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.jsonc similarity index 62% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.jsonc index 2d51fb7d06e..1a4c0e3d1de 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.jsonc @@ -1,4 +1,5 @@ //@ run: fail //@ description: Field index out of bounds should error ["::kernel::special_form::access", "data", { "#literal": 18446744073709551616 }] -//~^ ERROR Use a valid field index within usize bounds +//~^ ERROR field index is out of bounds +//~| ERROR cannot find value `data` in this scope diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.stderr new file mode 100644 index 00000000000..f805d8bd0e2 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-index-out-of-bounds.stderr @@ -0,0 +1,14 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": 18446744073709551616 }] + │ ━━━━ cannot find value `data` in this scope + │ + ├ help: check the spelling, or import the name with a `use` statement + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first + +error[expander::field-index-out-of-bounds]: Field index out of bounds + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": 18446744073709551616 }] + │ ━━━━━━━━━━━━━━━━━━━━ field index is out of bounds + │ + ╰ help: use a non-negative integer that fits within platform bounds \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.jsonc similarity index 58% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.jsonc index 19d39c52a1b..316cd176be1 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.jsonc @@ -1,4 +1,5 @@ //@ run: fail //@ description: Field access with boolean literal should error ["::kernel::special_form::access", "data", { "#literal": true }] -//~^ ERROR Use an integer literal here +//~^ ERROR expected an integer for field indexing +//~| ERROR cannot find value `data` in this scope diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.stderr new file mode 100644 index 00000000000..6addf9781df --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-boolean.stderr @@ -0,0 +1,14 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": true }] + │ ━━━━ cannot find value `data` in this scope + │ + ├ help: check the spelling, or import the name with a `use` statement + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first + +error[expander::invalid-field-literal-type]: Invalid field literal type + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": true }] + │ ━━━━ expected an integer for field indexing + │ + ╰ help: use an integer index like `0` for tuple field access, or a name like `field` for named field access \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.jsonc similarity index 60% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.jsonc index 9275f5e1def..70e26cfe4a9 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.jsonc @@ -1,4 +1,5 @@ //@ run: fail //@ description: Field access with string literal should error ["::kernel::special_form::access", "data", { "#literal": "field_name" }] -//~^ ERROR Use an integer literal here +//~^ ERROR expected an integer for field indexing +//~| ERROR cannot find value `data` in this scope diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.stderr new file mode 100644 index 00000000000..beaee1d5975 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-string.stderr @@ -0,0 +1,14 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": "field_name" }] + │ ━━━━ cannot find value `data` in this scope + │ + ├ help: check the spelling, or import the name with a `use` statement + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first + +error[expander::invalid-field-literal-type]: Invalid field literal type + ╭▸ +3 │ ["::kernel::special_form::access", "data", { "#literal": "field_name" }] + │ ━━━━━━━━━━━━ expected an integer for field indexing + │ + ╰ help: use an integer index like `0` for tuple field access, or a name like `field` for named field access \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.jsonc new file mode 100644 index 00000000000..017782b5004 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.jsonc @@ -0,0 +1,13 @@ +//@ run: fail +//@ description: Field literal with type annotation should error +[ + "let", + "tuple", + { "#tuple": [{ "#literal": 1 }] }, + [ + "::kernel::special_form::access", + "tuple", + { "#literal": 0, "#type": "Integer" } + //~^ ERROR type annotations are not allowed on field index literals + ] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.stderr new file mode 100644 index 00000000000..6529c982ec1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-type-annotation.stderr @@ -0,0 +1,6 @@ +error[expander::field-literal-type-annotation]: Field literal with type annotation + ╭▸ +10 │ { "#literal": 0, "#type": "Integer" } + │ ━━━━━━━ type annotations are not allowed on field index literals + │ + ╰ help: remove the type annotation and use a plain integer like `0` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.jsonc new file mode 100644 index 00000000000..15d1bddb055 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.jsonc @@ -0,0 +1,9 @@ +//@ run: pass +//@ description: Field access with valid integer literal should compile +[ + "fn", + { "#tuple": [] }, + { "#struct": { "x": "Integer" } }, + "_", + ["::kernel::special_form::access", "x", { "#literal": 0 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.stdout new file mode 100644 index 00000000000..ff38cad02e8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/access-field-literal-valid.stdout @@ -0,0 +1,21 @@ +Expr#4294967040@32 + ExprKind (Closure) + ClosureExpr#4294967040@32 + ClosureSignature#4294967040@32 + Generics#4294967040@4 + ClosureParam#4294967040@12 (name: x) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Type#4294967040@16 + TypeKind (Infer) + Expr#4294967040@31 + ExprKind (Field) + FieldExpr#4294967040@31 (field: 0) + Expr#4294967040@28 + ExprKind (Path) + Path#4294967040@28 (rooted: false) + PathSegment#4294967040@27 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.jsonc similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.jsonc index c293e3b9843..4ad47783430 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: is/1 should error out ["::kernel::special_form::as", { "#literal": true }] -//~^ ERROR Add missing arguments +//~^ ERROR expected 2 arguments to `as`, found 1 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.stderr new file mode 100644 index 00000000000..c8ef1518c2c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-1.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::as", { "#literal": true }] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 2 arguments to `as`, found 1 + │ + ├ help: use `(as value type)` + ╰ note: the first argument is the value to cast and the second is the target type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.jsonc index 5881fe702ce..80ab040fe46 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: as/2 should be converted to an AST node -["::kernel::special_form::as", { "#literal": true }, "Int"] +["::kernel::special_form::as", { "#literal": true }, "Integer"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.stdout similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.stdout index b4b6fcced0a..0540d952957 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-2.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-2.stdout @@ -7,5 +7,7 @@ Expr#4294967040@14 Primitive (True) Type#4294967040@13 TypeKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: Int) + Path#4294967040@13 (rooted: true) + PathSegment#4294967040@12 (name: kernel) + PathSegment#4294967040@12 (name: type) + PathSegment#4294967040@12 (name: Integer) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.jsonc similarity index 82% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.jsonc index b5d65d219a4..10a014bac28 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.jsonc @@ -5,5 +5,5 @@ { "#literal": true }, { "#literal": true }, { "#literal": true } - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.stderr new file mode 100644 index 00000000000..cf894a628f6 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/as-3.stderr @@ -0,0 +1,14 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ┏ [ +4 │ ┃ "::kernel::special_form::as", +5 │ ┃ { "#literal": true }, +6 │ ┃ { "#literal": true }, +7 │ ┃ { "#literal": true } + │ ┃ ──────────────────── unexpected argument +8 │ ┃ //~^ ERROR unexpected argument +9 │ ┃ ] + │ ┗━┛ expected 2 arguments to `as`, found 3 + │ + ├ help: use `(as value type)` + ╰ note: the first argument is the value to cast and the second is the target type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/does-not-touch-non-kernel.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/does-not-touch-non-kernel.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/does-not-touch-non-kernel.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/does-not-touch-non-kernel.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/does-not-touch-non-kernel.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/does-not-touch-non-kernel.stdout similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/does-not-touch-non-kernel.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/does-not-touch-non-kernel.stdout index 63f44f9f0dc..148e47c3265 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/does-not-touch-non-kernel.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/does-not-touch-non-kernel.stdout @@ -3,8 +3,10 @@ Expr#4294967040@8 CallExpr#4294967040@8 Expr#4294967040@3 ExprKind (Path) - Path#4294967040@3 (rooted: false) - PathSegment#4294967040@2 (name: +) + Path#4294967040@3 (rooted: true) + PathSegment#4294967040@2 (name: core) + PathSegment#4294967040@2 (name: math) + PathSegment#4294967040@2 (name: add) Argument#4294967040@5 Expr#4294967040@5 ExprKind (Literal) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.jsonc similarity index 72% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.jsonc index 512750875cc..64ba42deafa 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: fn/3 should error out ["::kernel::special_form::fn", { "#tuple": [] }, { "#struct": {} }, "_"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 4 arguments to `fn`, found 3 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.stderr new file mode 100644 index 00000000000..d2c361902f7 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-3.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::fn", { "#tuple": [] }, { "#struct": {} }, "_"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 4 arguments to `fn`, found 3 + │ + ├ help: use `(fn generics params return-type body)` + ╰ note: the arguments are, in order: the generic parameters (a tuple or struct), the function parameters (a struct), the return type, and the body expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.jsonc similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.jsonc index 53b4a2e8ad1..606b42b76ec 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::fn", { "#tuple": [] }, - { "#struct": {} }, + { "#struct": { "a": "Integer", "b": "Integer" } }, "_", ["+", "a", "b"] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.stdout new file mode 100644 index 00000000000..1f066578a2d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-4.stdout @@ -0,0 +1,40 @@ +Expr#4294967040@41 + ExprKind (Closure) + ClosureExpr#4294967040@41 + ClosureSignature#4294967040@41 + Generics#4294967040@8 + ClosureParam#4294967040@16 (name: a) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Integer) + ClosureParam#4294967040@23 (name: b) + Type#4294967040@22 + TypeKind (Path) + Path#4294967040@22 (rooted: true) + PathSegment#4294967040@21 (name: kernel) + PathSegment#4294967040@21 (name: type) + PathSegment#4294967040@21 (name: Integer) + Type#4294967040@27 + TypeKind (Infer) + Expr#4294967040@40 + ExprKind (Call) + CallExpr#4294967040@40 + Expr#4294967040@31 + ExprKind (Path) + Path#4294967040@31 (rooted: true) + PathSegment#4294967040@30 (name: core) + PathSegment#4294967040@30 (name: math) + PathSegment#4294967040@30 (name: add) + Argument#4294967040@35 + Expr#4294967040@35 + ExprKind (Path) + Path#4294967040@35 (rooted: false) + PathSegment#4294967040@34 (name: a) + Argument#4294967040@39 + Expr#4294967040@39 + ExprKind (Path) + Path#4294967040@39 (rooted: false) + PathSegment#4294967040@38 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.jsonc similarity index 83% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.jsonc index 223a4a70f0e..5969c8a0673 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.jsonc @@ -7,5 +7,5 @@ "_", ["+", "a", "b"], ["*", "c", "d"] - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.stderr new file mode 100644 index 00000000000..bc26092bbfb --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-5.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::fn", + 5 │ ┃ { "#tuple": [] }, + 6 │ ┃ { "#struct": {} }, + ‡ ┃ + 9 │ ┃ ["*", "c", "d"] + │ ┃ ─────────────── unexpected argument +10 │ ┃ //~^ ERROR unexpected argument +11 │ ┃ ] + │ ┗━┛ expected 4 arguments to `fn`, found 5 + │ + ├ help: use `(fn generics params return-type body)` + ╰ note: the arguments are, in order: the generic parameters (a tuple or struct), the function parameters (a struct), the return type, and the body expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.jsonc similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.jsonc index ca575ea650e..5fe08075c88 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.jsonc @@ -3,8 +3,8 @@ [ "::kernel::special_form::fn", { "#tuple": ["T", "T"] }, - //~^ ERROR Remove this duplicate generic parameter 'T' + //~^ ERROR duplicate generic parameter `T` { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.stderr new file mode 100644 index 00000000000..59e077a667a --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-duplicates.stderr @@ -0,0 +1,8 @@ +error[expander::duplicate-fn-generic]: Duplicate generic parameter + ╭▸ +5 │ { "#tuple": ["T", "T"] }, + │ ┬ ━ duplicate generic parameter `T` + │ │ + │ `T` was first declared here + │ + ╰ help: remove the duplicate declaration or use a different name \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.jsonc similarity index 65% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.jsonc index 02c429de959..e200972c04e 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.jsonc @@ -3,8 +3,8 @@ [ "::kernel::special_form::fn", { "#literal": 2 }, - //~^ ERROR Use a valid generics expression + //~^ ERROR expected a tuple or struct for generic parameters { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.stderr new file mode 100644 index 00000000000..2780d90573a --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-literal.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-fn-generics]: Invalid generic parameter list + ╭▸ +5 │ { "#literal": 2 }, + │ ━━━━━━━━━━━━━━━━━ expected a tuple or struct for generic parameters + │ + ╰ help: use a tuple like `(T, U)` for unbounded generics or a struct like `(T: bound, U: _)` for bounded generics \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.jsonc similarity index 74% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.jsonc index 762adbd2d5a..6d41b8ac8d0 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.jsonc @@ -5,10 +5,10 @@ { "#struct": { "T": { "#literal": 2 } - //~^ ERROR Replace this literal with a type name + //~^ ERROR cannot use a literal value as a type } }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.stderr new file mode 100644 index 00000000000..f60a3869aa6 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-literal.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +7 │ "T": { "#literal": 2 } + │ ━━━━━━━━━━━━━━━━━ cannot use a literal value as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.jsonc index c4f096225d7..cc426ec389a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.jsonc @@ -3,11 +3,11 @@ [ "::kernel::special_form::fn", { - "#struct": { "T": "Int" }, - "#type": "Int" - //~^ ERROR Remove this type annotation + "#struct": { "T": "Integer" }, + "#type": "Integer" + //~^ ERROR type annotation is not allowed on the generic parameter list }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.stderr new file mode 100644 index 00000000000..1dc11aa8672 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-type.stderr @@ -0,0 +1,7 @@ +error[expander::fn-generics-type-annotation]: Type annotation on generic parameter list + ╭▸ +7 │ "#type": "Integer" + │ ━━━━━━━ type annotation is not allowed on the generic parameter list + │ + ├ help: remove the type annotation; the generic parameter list declares type variables, not a typed value + ╰ note: write `(fn (T, U) ...)` for unbounded generics or `(fn (T: bound, U: _) ...)` for bounded generics \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.jsonc similarity index 89% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.jsonc index 9b81e580b6c..4dcbc3aa5cd 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.jsonc @@ -7,5 +7,5 @@ }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.stdout new file mode 100644 index 00000000000..338a4c8e11b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct-underscore.stdout @@ -0,0 +1,13 @@ +Expr#4294967040@21 + ExprKind (Closure) + ClosureExpr#4294967040@21 + ClosureSignature#4294967040@21 + Generics#4294967040@13 + GenericParam#4294967040@12 (name: T) + Type#4294967040@18 + TypeKind (Infer) + Expr#4294967040@20 + ExprKind (Literal) + LiteralExpr#4294967040@19 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.jsonc similarity index 69% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.jsonc index 0314eba772c..c1886cccc15 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.jsonc @@ -2,8 +2,8 @@ //@ description: fn/4 struct generics should compile [ "::kernel::special_form::fn", - { "#struct": { "T": "Int" } }, + { "#struct": { "T": "Integer" } }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.stdout new file mode 100644 index 00000000000..43181ff8ddd --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-struct.stdout @@ -0,0 +1,19 @@ +Expr#4294967040@23 + ExprKind (Closure) + ClosureExpr#4294967040@23 + ClosureSignature#4294967040@23 + Generics#4294967040@15 + GenericParam#4294967040@14 (name: T) + Type#4294967040@13 + TypeKind (Path) + Path#4294967040@13 (rooted: true) + PathSegment#4294967040@12 (name: kernel) + PathSegment#4294967040@12 (name: type) + PathSegment#4294967040@12 (name: Integer) + Type#4294967040@20 + TypeKind (Infer) + Expr#4294967040@22 + ExprKind (Literal) + LiteralExpr#4294967040@21 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.jsonc similarity index 74% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.jsonc index 3d164480745..9aa10fdb765 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.jsonc @@ -5,10 +5,10 @@ { "#tuple": [ { "#literal": 1 } - //~^ ERROR Use a valid generics expression + //~^ ERROR expected a simple type parameter name ] }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.stderr new file mode 100644 index 00000000000..7a5de4d08ff --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-literal.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-fn-generic-param]: Invalid generic parameter + ╭▸ +7 │ { "#literal": 1 } + │ ━━━━━━━━━━━━━━━━━ expected a simple type parameter name + │ + ╰ help: each generic parameter must be a plain identifier like `T`, not a qualified path or expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.jsonc similarity index 74% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.jsonc index f04165507b5..f72e747ccb5 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.jsonc @@ -5,10 +5,10 @@ { "#tuple": [ "::core::math::add" - //~^ ERROR Use a simple identifier here + //~^ ERROR expected a simple type parameter name ] }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.stderr new file mode 100644 index 00000000000..13a99c61f12 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-path.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-fn-generic-param]: Invalid generic parameter + ╭▸ +7 │ "::core::math::add" + │ ━━━━━━━━━━━━━━━━━ expected a simple type parameter name + │ + ╰ help: each generic parameter must be a plain identifier like `T`, not a qualified path or expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.jsonc similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.jsonc index 539748b003d..cf7456ee130 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.jsonc @@ -5,9 +5,9 @@ { "#tuple": ["T"], "#type": "Int" - //~^ ERROR Remove this type annotation + //~^ ERROR type annotation is not allowed on the generic parameter list }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.stderr new file mode 100644 index 00000000000..807b93613da --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple-type.stderr @@ -0,0 +1,7 @@ +error[expander::fn-generics-type-annotation]: Type annotation on generic parameter list + ╭▸ +7 │ "#type": "Int" + │ ━━━ type annotation is not allowed on the generic parameter list + │ + ├ help: remove the type annotation; the generic parameter list declares type variables, not a typed value + ╰ note: write `(fn (T, U) ...)` for unbounded generics or `(fn (T: bound, U: _) ...)` for bounded generics \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.jsonc similarity index 88% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.jsonc index 748ba6817f8..3c2a43686d6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.jsonc @@ -5,5 +5,5 @@ { "#tuple": ["T"] }, { "#struct": {} }, "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.stdout new file mode 100644 index 00000000000..ba1e20a7f34 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-generics-tuple.stdout @@ -0,0 +1,13 @@ +Expr#4294967040@20 + ExprKind (Closure) + ClosureExpr#4294967040@20 + ClosureSignature#4294967040@20 + Generics#4294967040@12 + GenericParam#4294967040@11 (name: T) + Type#4294967040@17 + TypeKind (Infer) + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.jsonc index a0efa899152..4d3574e99f8 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.jsonc @@ -3,8 +3,8 @@ [ "::kernel::special_form::fn", { "#struct": {} }, - { "#struct": { "a": "Int", "a": "Int" } }, - //~^ ERROR Remove this duplicate parameter 'a' + { "#struct": { "a": "Integer", "a": "Integer" } }, + //~^ ERROR duplicate parameter `a` "_", - ["+", "a", "b"] + ["+", "a", { "#literal": 0 }] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.stderr new file mode 100644 index 00000000000..719937bacd5 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-duplicates.stderr @@ -0,0 +1,8 @@ +error[expander::duplicate-fn-parameter]: Duplicate function parameter + ╭▸ +6 │ { "#struct": { "a": "Integer", "a": "Integer" } }, + │ ┬───────────── ━━━━━━━━━━━━━━ duplicate parameter `a` + │ │ + │ `a` was first declared here + │ + ╰ help: remove the duplicate parameter or use a different name \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.jsonc similarity index 67% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.jsonc index c1cf0d9077e..be0256250c2 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.jsonc @@ -4,7 +4,7 @@ "::kernel::special_form::fn", { "#struct": {} }, { "#tuple": [] }, - //~^ ERROR Use a struct expression for parameters + //~^ ERROR expected a struct for function parameters "_", - ["+", "a", "b"] + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.stderr new file mode 100644 index 00000000000..7600d622fd3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-tuple.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-fn-params]: Invalid parameter list + ╭▸ +6 │ { "#tuple": [] }, + │ ━━━━━━━━━━━━━━━━ expected a struct for function parameters + │ + ╰ help: write parameters as `(x: int, y: string)` where each field declares a parameter and its type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.jsonc similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.jsonc index f0f9a415ffb..d58f266ee0a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.jsonc @@ -6,8 +6,8 @@ { "#struct": {}, "#type": "Int" - //~^ ERROR Remove this type annotation + //~^ ERROR type annotation is not allowed on the parameter list }, "_", - ["+", "a", "b"] + ["+", { "#literal": 1 }, { "#literal": 2 }] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.stderr new file mode 100644 index 00000000000..94e9fce2f0b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/fn-params-type.stderr @@ -0,0 +1,6 @@ +error[expander::fn-params-type-annotation]: Type annotation on parameter list + ╭▸ +8 │ "#type": "Int" + │ ━━━ type annotation is not allowed on the parameter list + │ + ╰ help: remove the type annotation; each parameter already declares its type individually as `(name: type)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.jsonc new file mode 100644 index 00000000000..9fdc9d4c6e3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.jsonc @@ -0,0 +1,4 @@ +//@ run: fail +//@ description: If any generics are used in the path, error out +["::kernel::special_form::if"] +//~^ ERROR generic argument not allowed here diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.stderr new file mode 100644 index 00000000000..363e7346a09 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/generics.stderr @@ -0,0 +1,7 @@ +error[expander::generic-arguments-in-module]: Generic arguments in module path + ╭▸ +3 │ ["::kernel::special_form::if"] + │ ━━━━━━━ generic argument not allowed here + │ + ├ help: move the generic arguments to the final segment of the path, or remove them + ╰ note: modules are not generic; only the final item in a path can be parameterized \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.jsonc similarity index 66% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.jsonc index 1e29ca7758e..3329acc46d7 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: if/1 should error out ["::kernel::special_form::if", { "#literal": true }] -//~^ ERROR Add missing arguments +//~^ ERROR expected 2 or 3 arguments to `if`, found 1 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.stderr new file mode 100644 index 00000000000..f4d93bfcebb --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-1.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::if", { "#literal": true }] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 2 or 3 arguments to `if`, found 1 + │ + ├ help: use `(if condition then)` or `(if condition then else)` + ╰ note: the arguments are, in order: the condition, the then branch, and an optional else branch \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-2.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-2.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-2.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-2.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-2.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-2.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-3.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-3.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-3.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-3.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-3.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.jsonc similarity index 84% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.jsonc index 461a63578ba..ce2c226e398 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.jsonc @@ -6,5 +6,5 @@ { "#literal": true }, { "#literal": true }, { "#literal": true } - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.stderr new file mode 100644 index 00000000000..69c494fcdd0 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/if-4.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::if", + 5 │ ┃ { "#literal": true }, + 6 │ ┃ { "#literal": true }, + 7 │ ┃ { "#literal": true }, + 8 │ ┃ { "#literal": true } + │ ┃ ──────────────────── unexpected argument + 9 │ ┃ //~^ ERROR unexpected argument +10 │ ┃ ] + │ ┗━┛ expected 2 or 3 arguments to `if`, found 4 + │ + ├ help: use `(if condition then)` or `(if condition then else)` + ╰ note: the arguments are, in order: the condition, the then branch, and an optional else branch \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.jsonc similarity index 65% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.jsonc index 8dac82e56b3..e0baf13f119 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: index/1 should error out ["::kernel::special_form::index", "x"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 2 arguments to `[]`, found 1 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.stderr new file mode 100644 index 00000000000..4c6623c6a76 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-1.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::index", "x"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 2 arguments to `[]`, found 1 + │ + ├ help: use `([] collection index)` + ╰ note: the first argument is the collection and the second is the index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.jsonc new file mode 100644 index 00000000000..3819b7f96c8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: index/2 should compile +["::kernel::special_form::index", { "#literal": 1 }, { "#literal": 2 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.stdout new file mode 100644 index 00000000000..f707028a711 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-2.stdout @@ -0,0 +1,13 @@ +Expr#4294967040@12 + ExprKind (Index) + IndexExpr#4294967040@12 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) + Expr#4294967040@11 + ExprKind (Literal) + LiteralExpr#4294967040@10 + Primitive (Integer) + Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.jsonc similarity index 77% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.jsonc index cdf816cf881..f3f3a688a9d 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.jsonc @@ -5,5 +5,5 @@ "x", "y", "z" - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.stderr new file mode 100644 index 00000000000..84b0e8fdd8f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/index-3.stderr @@ -0,0 +1,14 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ┏ [ +4 │ ┃ "::kernel::special_form::index", +5 │ ┃ "x", +6 │ ┃ "y", +7 │ ┃ "z" + │ ┃ ─ unexpected argument +8 │ ┃ //~^ ERROR unexpected argument +9 │ ┃ ] + │ ┗━┛ expected 2 arguments to `[]`, found 3 + │ + ├ help: use `([] collection index)` + ╰ note: the first argument is the collection and the second is the index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.jsonc similarity index 62% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.jsonc index a956ac274bd..8c6d50b7151 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: input/1 should error out ["::kernel::special_form::input", "x"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 2 or 3 arguments to `input`, found 1 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.stderr new file mode 100644 index 00000000000..856ede72354 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-1.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::input", "x"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 2 or 3 arguments to `input`, found 1 + │ + ├ help: use `(input name type)` or `(input name type default)` + ╰ note: the arguments are, in order: the input name, its type, and an optional default value \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.jsonc index a4e9a8336e9..e9ce9a14b9c 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: input/2 should compile -["::kernel::special_form::input", "x", "Y"] +["::kernel::special_form::input", "x", "Integer"] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.stdout new file mode 100644 index 00000000000..87b14b256cf --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-2.stdout @@ -0,0 +1,9 @@ +Expr#4294967040@16 + ExprKind (Input) + InputExpr#4294967040@16 (name: x) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Integer) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.jsonc new file mode 100644 index 00000000000..2f1e0b19cda --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: input/3 should compile +["::kernel::special_form::input", "x", "Integer", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.stdout new file mode 100644 index 00000000000..026414ed8bc --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-3.stdout @@ -0,0 +1,14 @@ +Expr#4294967040@18 + ExprKind (Input) + InputExpr#4294967040@18 (name: x) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Integer) + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.jsonc similarity index 78% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.jsonc index 564aed159d5..3c986f6b23a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.jsonc @@ -6,5 +6,5 @@ "Y", "z", "w" - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.stderr new file mode 100644 index 00000000000..9bf9295a090 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/input-4.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::input", + 5 │ ┃ "x", + 6 │ ┃ "Y", + 7 │ ┃ "z", + 8 │ ┃ "w" + │ ┃ ─ unexpected argument + 9 │ ┃ //~^ ERROR unexpected argument +10 │ ┃ ] + │ ┗━┛ expected 2 or 3 arguments to `input`, found 4 + │ + ├ help: use `(input name type)` or `(input name type default)` + ╰ note: the arguments are, in order: the input name, its type, and an optional default value \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.jsonc similarity index 78% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.jsonc index 8bc61922cb9..07ac6e67ed4 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.jsonc @@ -4,7 +4,7 @@ "::kernel::special_form::if", { ":test": { "#literal": true } - //~^ ERROR this labeled argument + //~^ ERROR labeled arguments are not allowed in `if` }, { "#literal": true }, { "#literal": false } diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.stderr new file mode 100644 index 00000000000..c1099ff418d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/labeled-argument.stderr @@ -0,0 +1,6 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +6 │ ":test": { "#literal": true } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in `if` + │ + ╰ help: pass the arguments positionally: `(if condition then)` or `(if condition then else)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.jsonc similarity index 67% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.jsonc index 8874c97f387..421331d8cf8 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: let/2 should error out ["::kernel::special_form::let", "x", { "#literal": true }] -//~^ ERROR Add missing arguments +//~^ ERROR expected 3 or 4 arguments to `let`, found 2 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.stderr new file mode 100644 index 00000000000..ef7e4209d33 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-2.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::let", "x", { "#literal": true }] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 3 or 4 arguments to `let`, found 2 + │ + ├ help: use `(let name value body)` or `(let name type value body)` + ╰ note: the arguments are, in order: the binding name, an optional type annotation, the bound value, and the body expression where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-3.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-3.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-3.stdout similarity index 77% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-3.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-3.stdout index 52207bde75d..fd4e1fb4f5a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-3.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-3.stdout @@ -10,8 +10,10 @@ Expr#4294967040@27 CallExpr#4294967040@26 Expr#4294967040@17 ExprKind (Path) - Path#4294967040@17 (rooted: false) - PathSegment#4294967040@16 (name: +) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: core) + PathSegment#4294967040@16 (name: math) + PathSegment#4294967040@16 (name: add) Argument#4294967040@21 Expr#4294967040@21 ExprKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-4.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-4.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-4.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-4.stdout similarity index 69% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-4.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-4.stdout index cb564b62eeb..8198b3ed148 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-4.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-4.stdout @@ -7,15 +7,19 @@ Expr#4294967040@31 Primitive (True) Type#4294967040@15 TypeKind (Path) - Path#4294967040@15 (rooted: false) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) PathSegment#4294967040@14 (name: Boolean) Expr#4294967040@30 ExprKind (Call) CallExpr#4294967040@30 Expr#4294967040@21 ExprKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: +) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: core) + PathSegment#4294967040@20 (name: math) + PathSegment#4294967040@20 (name: add) Argument#4294967040@25 Expr#4294967040@25 ExprKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.jsonc similarity index 83% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.jsonc index 4f344140fa4..3e61fec9b42 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.jsonc @@ -7,5 +7,5 @@ { "#literal": true }, ["+", "x", "x"], { "#literal": true } - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.stderr new file mode 100644 index 00000000000..ca3ab689ef4 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-5.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::let", + 5 │ ┃ "x", + 6 │ ┃ "Boolean", + ‡ ┃ + 9 │ ┃ { "#literal": true } + │ ┃ ──────────────────── unexpected argument +10 │ ┃ //~^ ERROR unexpected argument +11 │ ┃ ] + │ ┗━┛ expected 3 or 4 arguments to `let`, found 5 + │ + ├ help: use `(let name value body)` or `(let name type value body)` + ╰ note: the arguments are, in order: the binding name, an optional type annotation, the bound value, and the body expression where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.jsonc new file mode 100644 index 00000000000..90300dd49f2 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.jsonc @@ -0,0 +1,21 @@ +//@ run: fail +//@ description: The binding name must be a identifier +[ + "::kernel::special_form::let", + "Int", + //~^ ERROR expected a simple identifier for the `let` binding name + { "#literal": true }, + [ + "::kernel::special_form::let", + "x::A", + //~^ ERROR expected a simple identifier for the `let` binding name + { "#literal": true }, + [ + "::kernel::special_form::let", + "::z", + //~^ ERROR expected a simple identifier for the `let` binding name + { "#literal": true }, + ["+", { "#literal": true }, { "#literal": true }] + ] + ] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.stderr new file mode 100644 index 00000000000..7e27162c781 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-ident.stderr @@ -0,0 +1,23 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +15 │ "::z", + │ ━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments + +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +10 │ "x::A", + │ ━━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments + +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +5 │ "Int", + │ ━━━━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.jsonc similarity index 56% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.jsonc index c19f5c8eafd..70353a296e0 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::let", { "#literal": true }, - //~^ ERROR Replace this expression with a simple identifier + //~^ ERROR expected a simple identifier for the `let` binding name { "#literal": true }, - ["+", "x", "x"] + ["+", { "#literal": true }, { "#literal": true }] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.stderr new file mode 100644 index 00000000000..6f8d2ceb310 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-argument-not-path.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +5 │ { "#literal": true }, + │ ━━━━━━━━━━━━━━━━━━━━ expected a simple identifier for the `let` binding name + │ + ├ help: write `(let name value body)` with a plain name such as `x` + ╰ note: the first argument to `let` introduces a new local binding and must be a plain identifier without path qualification or generic arguments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.jsonc index c3a89bb5da0..b3ed9ed4a36 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.jsonc @@ -2,10 +2,10 @@ //@ description: We should collect all errors when lowering let/4, and not only report the first one. [ "::kernel::special_form::let", - "::x", - //~^ ERROR Replace this with a simple identifier - { "#literal": 1 }, - //~^ ERROR Replace this literal with a type name + "x", { "#literal": 1 }, + //~^ ERROR cannot use a literal value as a type + "y", + //~^ ERROR cannot find value `y` in this scope "x" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.stderr new file mode 100644 index 00000000000..4c8048f7cf5 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/let-collect-all-errors.stderr @@ -0,0 +1,31 @@ +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +6 │ { "#literal": 1 }, + │ ━━━━━━━━━━━━━━━━━ cannot use a literal value as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +8 │ "y", + │ ━ cannot find value `y` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +8 - "y", +8 + "↑", + ├╴ +8 - "y", +8 + "√", + ├╴ +8 - "y", +8 + "∛", + ├╴ +8 - "y", +8 + "newtype", + ├╴ +8 - "y", +8 + "type", + ╰╴ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.jsonc similarity index 67% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.jsonc index 82e830dec5d..ceee6ef7315 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: newtype/2 should error out ["::kernel::special_form::newtype", "x", "Integer"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 3 arguments to `newtype`, found 2 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.stderr new file mode 100644 index 00000000000..6d5804f3fed --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-2.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::newtype", "x", "Integer"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 3 arguments to `newtype`, found 2 + │ + ├ help: use `(newtype Name type-expr body)` + ╰ note: the arguments are, in order: the type name (optionally with generic parameters), the underlying type, and the body where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-3.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-3.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-3.stdout similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-3.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-3.stdout index bb964e5dd05..be472c39786 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-3.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-3.stdout @@ -3,7 +3,9 @@ Expr#4294967040@20 NewTypeExpr#4294967040@20 (name: x) Type#4294967040@15 TypeKind (Path) - Path#4294967040@15 (rooted: false) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) PathSegment#4294967040@14 (name: Integer) Expr#4294967040@19 ExprKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.jsonc similarity index 62% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.jsonc index ecdcfa0fdf8..990df03e74a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.jsonc @@ -4,7 +4,7 @@ "::kernel::special_form::newtype", "x", "Boolean", - ["+", "x", "x"], + ["+", { "#literal": true }, { "#literal": false }], { "#literal": true } - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.stderr new file mode 100644 index 00000000000..61d0c37b285 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-4.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::newtype", + 5 │ ┃ "x", + 6 │ ┃ "Boolean", + 7 │ ┃ ["+", { "#literal": true }, { "#literal": false }], + 8 │ ┃ { "#literal": true } + │ ┃ ──────────────────── unexpected argument + 9 │ ┃ //~^ ERROR unexpected argument +10 │ ┃ ] + │ ┗━┛ expected 3 arguments to `newtype`, found 4 + │ + ├ help: use `(newtype Name type-expr body)` + ╰ note: the arguments are, in order: the type name (optionally with generic parameters), the underlying type, and the body where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.jsonc similarity index 55% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.jsonc index 65360071140..991c535608e 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.jsonc @@ -1,5 +1,6 @@ //@ run: fail //@ description: We should collect all errors when lowering newtype/3, and not only report the first one. ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] -//~^ ERROR Replace this qualified path with a simple identifier -//~| ERROR Replace this literal with a type name +//~^ ERROR expected a type name for the `newtype` binding +//~| ERROR cannot use a literal value as a type +//~| ERROR cannot find value `x` in this scope diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.stderr new file mode 100644 index 00000000000..7b8d68ece10 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-collect-all-errors.stderr @@ -0,0 +1,38 @@ +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] + │ ━━━━━━━━━━━━━━━━━ cannot use a literal value as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] + │ ━ cannot find value `x` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +3 - ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "~"] + ├╴ +3 - ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "↑"] + ├╴ +3 - ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "√"] + ├╴ +3 - ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "∛"] + ├╴ +3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "index"] + ╰╴ ++++ + +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] + │ ━━━ expected a type name for the `newtype` binding + │ + ├ help: write `(newtype Name type-expr body)` with a name like `UserId` or `Pair` + ╰ note: the first argument to `newtype` introduces a new distinct type and must be a simple identifier, optionally with generic parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.jsonc index 2ea18aca8fa..ba45123328a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Constraints should be converted to an AST node -["::kernel::special_form::newtype", "Foo", "Integer", "_"] +["::kernel::special_form::newtype", "Foo", "Integer", "_"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.stdout similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.stdout index 76cc78cc0b6..1027e5e81e3 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-constraints.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/newtype-constraints.stdout @@ -5,11 +5,15 @@ Expr#4294967040@28 GenericConstraint#4294967040@19 (name: U) Type#4294967040@18 TypeKind (Path) - Path#4294967040@18 (rooted: false) - PathSegment#4294967040@17 (name: Bar) + Path#4294967040@18 (rooted: true) + PathSegment#4294967040@17 (name: kernel) + PathSegment#4294967040@17 (name: type) + PathSegment#4294967040@17 (name: Number) Type#4294967040@25 TypeKind (Path) - Path#4294967040@25 (rooted: false) + Path#4294967040@25 (rooted: true) + PathSegment#4294967040@24 (name: kernel) + PathSegment#4294967040@24 (name: type) PathSegment#4294967040@24 (name: Integer) Expr#4294967040@27 ExprKind (Underscore) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.jsonc similarity index 73% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.jsonc index 56d2185cb0a..e3be5a5e22f 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.jsonc @@ -2,7 +2,7 @@ //@ description: check if we error out if the path has not enough segments [ "::kernel::special_form", - //~^ ERROR exactly 3 segments + //~^ ERROR cannot find value `special_form` in module `::kernel` { "#literal": true }, { "#literal": 1 }, { "#literal": 2 } diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.stderr new file mode 100644 index 00000000000..af945c4f771 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/not-enough-segments.stderr @@ -0,0 +1,8 @@ +error[expander::item-not-found]: Item not found + ╭▸ +4 │ "::kernel::special_form", + │ ┬───── ━━━━━━━━━━━━ cannot find value `special_form` in module `::kernel` + │ │ + │ looked in this module + │ + ╰ help: `special_form` exists as a module, not value \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.jsonc similarity index 78% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.jsonc index 0cabae514bc..f22d2433ee0 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.jsonc @@ -2,7 +2,7 @@ //@ description: check if we error out if the path has too many segments [ "::kernel::special_form::if::extra", - //~^ ERROR Remove this extra segment + //~^ ERROR cannot access items inside a non-module { "#literal": true }, { "#literal": 1 }, { "#literal": 2 } diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.stderr new file mode 100644 index 00000000000..7894e15a36c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/too-many-segments.stderr @@ -0,0 +1,9 @@ +error[expander::module-required]: Module required + ╭▸ +4 │ "::kernel::special_form::if::extra", + │ ┯━ ───── cannot access items inside a non-module + │ │ + │ `::kernel::special_form::if` is a value, not a module + │ + ├ help: if you meant to access a field, use `.` instead of `::` + ╰ note: the `::` separator navigates into modules; values and types do not contain sub-items \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.jsonc similarity index 67% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.jsonc index 95916919da9..c04259b99db 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: type/2 should error out ["::kernel::special_form::type", "x", "Integer"] -//~^ ERROR Add missing arguments +//~^ ERROR expected 3 arguments to `type`, found 2 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.stderr new file mode 100644 index 00000000000..df43df456ba --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-2.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::type", "x", "Integer"] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 3 arguments to `type`, found 2 + │ + ├ help: use `(type Name type-expr body)` + ╰ note: the arguments are, in order: the type name (optionally with generic parameters), the type definition, and the body where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.jsonc index 7a5d3f020aa..d4701d37235 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: type/3 should be converted to an AST node -["::kernel::special_form::type", "x", "Integer", "x"] +["::kernel::special_form::type", "x", "Integer", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.stdout new file mode 100644 index 00000000000..1c60b665fce --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-3.stdout @@ -0,0 +1,14 @@ +Expr#4294967040@18 + ExprKind (Type) + TypeExpr#4294967040@18 (name: x) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Integer) + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.jsonc similarity index 82% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.jsonc index 1ba22332b8d..afb2ee2f482 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.jsonc @@ -6,5 +6,5 @@ "Boolean", ["+", "x", "x"], { "#literal": true } - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.stderr new file mode 100644 index 00000000000..1d81416e70c --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-4.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::type", + 5 │ ┃ "x", + 6 │ ┃ "Boolean", + 7 │ ┃ ["+", "x", "x"], + 8 │ ┃ { "#literal": true } + │ ┃ ──────────────────── unexpected argument + 9 │ ┃ //~^ ERROR unexpected argument +10 │ ┃ ] + │ ┗━┛ expected 3 arguments to `type`, found 4 + │ + ├ help: use `(type Name type-expr body)` + ╰ note: the arguments are, in order: the type name (optionally with generic parameters), the type definition, and the body where the name is in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.jsonc similarity index 54% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.jsonc index f8958b0cd3d..8ec8655441b 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.jsonc @@ -1,5 +1,6 @@ //@ run: fail //@ description: We should collect all errors when lowering type/3, and not only report the first one. ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] -//~^ ERROR Replace this qualified path with a simple identifier -//~| ERROR Replace this literal with a type name +//~^ ERROR expected a type name for the `type` binding +//~| ERROR cannot use a literal value as a type +//~| ERROR cannot find value `x` in this scope diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.stderr new file mode 100644 index 00000000000..6bae582536e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-collect-all-errors.stderr @@ -0,0 +1,38 @@ +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] + │ ━━━━━━━━━━━━━━━━━ cannot use a literal value as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] + │ ━ cannot find value `x` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +3 - ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::type", "::x", { "#literal": 1 }, "~"] + ├╴ +3 - ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::type", "::x", { "#literal": 1 }, "↑"] + ├╴ +3 - ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::type", "::x", { "#literal": 1 }, "√"] + ├╴ +3 - ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] +3 + ["::kernel::special_form::type", "::x", { "#literal": 1 }, "∛"] + ├╴ +3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "index"] + ╰╴ ++++ + +error[expander::invalid-binding-name]: Invalid binding name + ╭▸ +3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] + │ ━━━ expected a type name for the `type` binding + │ + ├ help: write `(type Name type-expr body)` with a name like `MyType` or `Pair` + ╰ note: the first argument to `type` introduces a new type alias and must be a simple identifier, optionally with generic parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.jsonc index 1faa827e8d8..aeb24c32c0f 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.jsonc @@ -3,6 +3,6 @@ [ "::kernel::special_form::as", { "#literal": true }, - ["::core::math::headpat", "Int", "Float"] - //~^ ERROR This function cannot be used as a type constructor + ["::core::math::headpat", "Integer", "Number"] + //~^ ERROR cannot find type `headpat` in module `::core::math` ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.stderr new file mode 100644 index 00000000000..24306636266 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-arbitrary.stderr @@ -0,0 +1,8 @@ +error[expander::item-not-found]: Item not found + ╭▸ +6 │ ["::core::math::headpat", "Integer", "Number"] + │ ┬─── ━━━━━━━ cannot find type `headpat` in module `::core::math` + │ │ + │ looked in this module + │ + ╰ help: check the spelling and ensure the item is exported \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.jsonc new file mode 100644 index 00000000000..2d5e6546aaa --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: Invoking `::kernel::types::intersection` should convert into a intersection type expression +["::kernel::special_form::as", { "#literal": true }, ["&", "Integer", "Number"]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.stdout new file mode 100644 index 00000000000..b21f91e6713 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-intersection.stdout @@ -0,0 +1,22 @@ +Expr#4294967040@23 + ExprKind (As) + AsExpr#4294967040@23 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (True) + Type#4294967040@22 + TypeKind (Intersection) + IntersectionType#4294967040@22 + Type#4294967040@17 + TypeKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: kernel) + PathSegment#4294967040@16 (name: type) + PathSegment#4294967040@16 (name: Integer) + Type#4294967040@21 + TypeKind (Path) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: kernel) + PathSegment#4294967040@20 (name: type) + PathSegment#4294967040@20 (name: Number) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.jsonc similarity index 62% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.jsonc index 17fc10a5dce..0a2af164532 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.jsonc @@ -5,8 +5,9 @@ { "#literal": true }, [ ["+", { "#literal": 1 }, { "#literal": 2 }], - //~^ ERROR Function call with non-path callee cannot be used as a type - "Int", - "Float" + //~^ ERROR cannot find type `+` in this scope + //~| ERROR cannot use this as a type constructor + "Integer", + "Number" ] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.stderr new file mode 100644 index 00000000000..cd59c90e367 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-not-path.stderr @@ -0,0 +1,37 @@ +error[expander::invalid-type-constructor-call]: Invalid type constructor call + ╭▸ + 6 │ ┌ [ + 7 │ │ ["+", { "#literal": 1 }, { "#literal": 2 }], + │ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ cannot use this as a type constructor + ‡ │ +11 │ │ "Number" +12 │ │ ] + │ └───┘ this call appears in a type position + │ + ├ help: use a type path directly, or use `(| ...)` / `(& ...)` for unions and intersections + ╰ note: generic type arguments belong on the type path itself, for example `Foo`, not `(Foo T)` + +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +7 │ ["+", { "#literal": 1 }, { "#literal": 2 }], + │ ━ cannot find type `+` in this scope + │ + ├ help: `+` exists as a value, not type + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +7 - ["+", { "#literal": 1 }, { "#literal": 2 }], +7 + ["String", { "#literal": 1 }, { "#literal": 2 }], + ├╴ +7 - ["+", { "#literal": 1 }, { "#literal": 2 }], +7 + ["Union", { "#literal": 1 }, { "#literal": 2 }], + ├╴ +7 - ["+", { "#literal": 1 }, { "#literal": 2 }], +7 + ["Unknown", { "#literal": 1 }, { "#literal": 2 }], + ├╴ +7 - ["+", { "#literal": 1 }, { "#literal": 2 }], +7 + ["Url", { "#literal": 1 }, { "#literal": 2 }], + ├╴ +7 - ["+", { "#literal": 1 }, { "#literal": 2 }], +7 + ["|", { "#literal": 1 }, { "#literal": 2 }], + ╰╴ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.jsonc new file mode 100644 index 00000000000..b5a9d96b9ea --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: Invoking `::kernel::types::union` should convert into a union type expression +["::kernel::special_form::as", { "#literal": true }, ["|", "Integer", "Number"]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.stdout new file mode 100644 index 00000000000..0e3e6107fe4 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-call-union.stdout @@ -0,0 +1,22 @@ +Expr#4294967040@23 + ExprKind (As) + AsExpr#4294967040@23 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (True) + Type#4294967040@22 + TypeKind (Union) + UnionType#4294967040@22 + Type#4294967040@17 + TypeKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: kernel) + PathSegment#4294967040@16 (name: type) + PathSegment#4294967040@16 (name: Integer) + Type#4294967040@21 + TypeKind (Path) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: kernel) + PathSegment#4294967040@20 (name: type) + PathSegment#4294967040@20 (name: Number) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.jsonc new file mode 100644 index 00000000000..b9b15329e3b --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.jsonc @@ -0,0 +1,24 @@ +//@ run: fail +//@ description: every expression, except for struct, tuple and paths are not valid type expressions +[ + "::kernel::special_form::as", + [ + "::kernel::special_form::as", + [ + "::kernel::special_form::as", + [ + "::kernel::special_form::as", + { "#literal": 1 }, + // ensure that we properly resolve nested is expressions as errors + ["::kernel::special_form::as", { "#literal": 1 }, "Integer"] + //~^ ERROR cannot find type `as` in module `::kernel::special_form` + ], + { "#literal": 1 } + //~^ ERROR cannot use a literal value as a type + ], + { "#list": [{ "#literal": 1 }, { "#literal": 2 }] } + //~^ ERROR cannot use a list expression as a type + ], + { "#dict": { "a": { "#literal": 1 } } } + //~^ ERROR cannot use a dictionary expression as a type +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.stderr new file mode 100644 index 00000000000..8e356b740d8 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-invalid.stderr @@ -0,0 +1,32 @@ +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +22 │ { "#dict": { "a": { "#literal": 1 } } } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ cannot use a dictionary expression as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +16 │ { "#literal": 1 } + │ ━━━━━━━━━━━━━━━━━ cannot use a literal value as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::invalid-expression-in-type-position]: Invalid expression in type position + ╭▸ +19 │ { "#list": [{ "#literal": 1 }, { "#literal": 2 }] } + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ cannot use a list expression as a type + │ + ├ help: replace this with a type path, tuple type, struct type, `_`, `(| ...)`, or `(& ...)` + ╰ note: valid type forms are: type paths like `String`, tuple types like `(Int, String)`, struct types like `(name: String)`, `_` for inference, unions `(| ...)`, and intersections `(& ...)` + +error[expander::item-not-found]: Item not found + ╭▸ +13 │ ["::kernel::special_form::as", { "#literal": 1 }, "Integer"] + │ ┬─────────── ━━ cannot find type `as` in module `::kernel::special_form` + │ │ + │ looked in this module + │ + ╰ help: `as` exists as a value, not type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.jsonc similarity index 69% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.jsonc index 5d67b780650..7bf31469f27 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.jsonc @@ -3,5 +3,5 @@ [ "::kernel::special_form::as", { "#literal": true }, - { "#struct": { "name": "Name", "birthday": "Date" } } + { "#struct": { "name": "String", "birthday": "Integer" } } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.stdout new file mode 100644 index 00000000000..8fe50db98e3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-struct.stdout @@ -0,0 +1,24 @@ +Expr#4294967040@26 + ExprKind (As) + AsExpr#4294967040@26 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (True) + Type#4294967040@25 + TypeKind (Struct) + StructType#4294967040@25 + StructField#4294967040@16 (name: name) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: String) + StructField#4294967040@23 (name: birthday) + Type#4294967040@22 + TypeKind (Path) + Path#4294967040@22 (rooted: true) + PathSegment#4294967040@21 (name: kernel) + PathSegment#4294967040@21 (name: type) + PathSegment#4294967040@21 (name: Integer) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.jsonc similarity index 78% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.jsonc index fe4faa8276f..3808b996eb6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.jsonc @@ -3,5 +3,5 @@ [ "::kernel::special_form::as", { "#literal": true }, - { "#tuple": ["Name", "Date"] } + { "#tuple": ["Integer", "String"] } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.stdout new file mode 100644 index 00000000000..ed81bb50727 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-expr-tuple.stdout @@ -0,0 +1,24 @@ +Expr#4294967040@20 + ExprKind (As) + AsExpr#4294967040@20 + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (True) + Type#4294967040@19 + TypeKind (Tuple) + TupleType#4294967040@19 + TupleField#4294967040@13 + Type#4294967040@13 + TypeKind (Path) + Path#4294967040@13 (rooted: true) + PathSegment#4294967040@12 (name: kernel) + PathSegment#4294967040@12 (name: type) + PathSegment#4294967040@12 (name: Integer) + TupleField#4294967040@17 + Type#4294967040@17 + TypeKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@16 (name: kernel) + PathSegment#4294967040@16 (name: type) + PathSegment#4294967040@16 (name: String) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.jsonc similarity index 62% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.jsonc index 9e71854a913..d58be4ca50a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::type", "Foo<::foo::Bar>", - //~^ ERROR Generic arguments must be simple identifiers - { "#struct": { "foo": "Bar" } }, + //~^ ERROR expected a simple type parameter + { "#struct": { "foo": "Integer" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.stderr new file mode 100644 index 00000000000..703408d484d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-absolute-path.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-generic-argument]: Invalid generic argument + ╭▸ +5 │ "Foo<::foo::Bar>", + │ ━━━━━━━━━━ expected a simple type parameter + │ + ├ help: use a simple identifier like `T`, not a qualified path + ╰ note: generic parameters are declared as `Name` for unconstrained or `Name` for constrained parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.jsonc similarity index 88% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.jsonc index 4e22523845c..76ebf5a4aff 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.jsonc @@ -2,7 +2,7 @@ //@ description: Generics with constraints should be properly lowered [ "::kernel::special_form::type", - "Foo", + "Foo", { "#struct": { "foo": "Bar" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.stdout similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.stdout index 0201b12b414..bed8e8b104c 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-constraint.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-constraint.stdout @@ -4,11 +4,13 @@ Expr#4294967040@28 GenericConstraint#4294967040@14 (name: Bar) Type#4294967040@13 TypeKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: Baz) - Type#4294967040@24 + Path#4294967040@13 (rooted: true) + PathSegment#4294967040@12 (name: kernel) + PathSegment#4294967040@12 (name: type) + PathSegment#4294967040@12 (name: String) + Type#4294967040@25 TypeKind (Struct) - StructType#4294967040@24 + StructType#4294967040@25 StructField#4294967040@23 (name: foo) Type#4294967040@22 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.jsonc similarity index 53% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.jsonc index e2d3394152c..7220593bfcf 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.jsonc @@ -2,8 +2,8 @@ //@ description: Duplicate generic constraints should be rejected [ "::kernel::special_form::type", - "Foo", - //~^ ERROR Remove this duplicate declaration of 'T' - { "#struct": { "foo": "Bar" } }, + "Foo", + //~^ ERROR duplicate generic parameter `T` + { "#struct": { "foo": "Integer" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.stderr new file mode 100644 index 00000000000..41d26e35538 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-duplicate.stderr @@ -0,0 +1,8 @@ +error[expander::duplicate-generic-constraint]: Duplicate generic constraint + ╭▸ +5 │ "Foo", + │ ┬ ━ duplicate generic parameter `T` + │ │ + │ `T` was first declared here + │ + ╰ help: remove the duplicate declaration or use a different name \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.jsonc similarity index 60% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.jsonc index 5bfeb06fda2..b579e0f0b18 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::type", "Foo>", - //~^ ERROR Generic arguments must be simple identifiers - { "#struct": { "foo": "Bar" } }, + //~^ ERROR expected a simple type parameter + { "#struct": { "foo": "Integer" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.stderr new file mode 100644 index 00000000000..0e3af90cfd5 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-generic-ident.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-generic-argument]: Invalid generic argument + ╭▸ +5 │ "Foo>", + │ ━━━━━━━━ expected a simple type parameter + │ + ├ help: use a simple identifier like `T`, not a qualified path + ╰ note: generic parameters are declared as `Name` for unconstrained or `Name` for constrained parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.jsonc similarity index 61% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.jsonc index 5b96bbdb5b2..fd5e20682de 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::type", "Foo", - //~^ ERROR Generic arguments must be simple identifiers - { "#struct": { "foo": "Bar" } }, + //~^ ERROR expected a simple type parameter + { "#struct": { "foo": "Integer" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.stderr new file mode 100644 index 00000000000..b28f1d4c2ac --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-relative-path.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-generic-argument]: Invalid generic argument + ╭▸ +5 │ "Foo", + │ ━━━━━━━━ expected a simple type parameter + │ + ├ help: use a simple identifier like `T`, not a qualified path + ╰ note: generic parameters are declared as `Name` for unconstrained or `Name` for constrained parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.jsonc similarity index 59% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.jsonc index 9da8fcd96b7..1b011e5c59e 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.jsonc @@ -3,7 +3,7 @@ [ "::kernel::special_form::type", "Foo<(a: String)>", - //~^ ERROR Generic argument types must be simple path identifiers - { "#struct": { "foo": "Bar" } }, + //~^ ERROR expected a simple type parameter + { "#struct": { "foo": "Integer" } }, "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.stderr new file mode 100644 index 00000000000..4eded93d4d2 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic-struct.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-generic-argument]: Invalid generic argument + ╭▸ +5 │ "Foo<(a: String)>", + │ ━━━━━━━━━━━ expected a simple type parameter + │ + ├ help: use a simple identifier like `T` or a constraint like `T: Bound` + ╰ note: generic parameters are declared as `Name` for unconstrained or `Name` for constrained parameters \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic.stdout similarity index 87% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic.stdout rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic.stdout index e5e62631a6c..6582c8ae8a6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-generic.stdout @@ -2,9 +2,9 @@ Expr#4294967040@28 ExprKind (Type) TypeExpr#4294967040@28 (name: Foo) GenericConstraint#4294967040@14 (name: Bar) - Type#4294967040@24 + Type#4294967040@25 TypeKind (Struct) - StructType#4294967040@24 + StructType#4294967040@25 StructField#4294967040@23 (name: foo) Type#4294967040@22 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.jsonc index 17334b2c226..bf1a95451e7 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Infer type should be properly lowered -["::kernel::special_form::type", "x", "_", "x"] +["::kernel::special_form::type", "x", "_", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.stdout new file mode 100644 index 00000000000..0002e5ffcf2 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-infer.stdout @@ -0,0 +1,10 @@ +Expr#4294967040@16 + ExprKind (Type) + TypeExpr#4294967040@16 (name: x) + Type#4294967040@13 + TypeKind (Infer) + Expr#4294967040@15 + ExprKind (Literal) + LiteralExpr#4294967040@14 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.jsonc similarity index 50% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.jsonc index afbea7c0cb9..84d2daedc6d 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Never type should be properly lowered -["::kernel::special_form::type", "x", "::kernel::type::Never", "x"] +["::kernel::special_form::type", "x", "!", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.stdout new file mode 100644 index 00000000000..e64d265f970 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-never.stdout @@ -0,0 +1,14 @@ +Expr#4294967040@18 + ExprKind (Type) + TypeExpr#4294967040@18 (name: x) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Never) + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.jsonc similarity index 69% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.jsonc index ac2ee070fa8..dbad559e616 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.jsonc @@ -6,7 +6,7 @@ { "#struct": { "name": "String" }, "#type": "(name: String)" - //~^ ERROR Remove this type annotation + //~^ ERROR type annotations are not allowed inside a type expression }, - "x" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.stderr new file mode 100644 index 00000000000..355a7d92833 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-struct-has-type.stderr @@ -0,0 +1,12 @@ +error[expander::type-annotation-in-type-position]: Type annotation in type position + ╭▸ + 6 │ ┌ { + 7 │ │ "#struct": { "name": "String" }, + 8 │ │ "#type": "(name: String)" + │ │ ━━━━━━━━━━━━━━ type annotations are not allowed inside a type expression + 9 │ │ //~^ ERROR type annotations are not allowed inside a type expression +10 │ │ }, + │ └───┘ this struct is already being used as a type + │ + ├ help: remove the type annotation + ╰ note: in type position, `(name: String)` already defines a struct type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.jsonc similarity index 50% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.jsonc index 8f233439e7f..bd460dc08a8 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.jsonc @@ -4,9 +4,9 @@ "::kernel::special_form::type", "x", { - "#tuple": ["Int"], - "#type": "(Int)" - //~^ ERROR Remove this type annotation + "#tuple": ["Integer"], + "#type": "(Integer)" + //~^ ERROR type annotations are not allowed inside a type expression }, - "x" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.stderr new file mode 100644 index 00000000000..a000a84d07f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-tuple-has-type.stderr @@ -0,0 +1,12 @@ +error[expander::type-annotation-in-type-position]: Type annotation in type position + ╭▸ + 6 │ ┌ { + 7 │ │ "#tuple": ["Integer"], + 8 │ │ "#type": "(Integer)" + │ │ ━━━━━━━━━ type annotations are not allowed inside a type expression + 9 │ │ //~^ ERROR type annotations are not allowed inside a type expression +10 │ │ }, + │ └───┘ this tuple is already being used as a type + │ + ├ help: remove the type annotation + ╰ note: in type position, `(Int, String)` already defines a tuple type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.jsonc similarity index 50% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.jsonc index 6fa96605454..2c245a5a6fb 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Unknown type should be properly lowered -["::kernel::special_form::type", "x", "::kernel::type::Unknown", "x"] +["::kernel::special_form::type", "x", "?", { "#literal": null }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.stdout new file mode 100644 index 00000000000..6c73cca03dc --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/type-unknown.stdout @@ -0,0 +1,13 @@ +Expr#4294967040@18 + ExprKind (Type) + TypeExpr#4294967040@18 (name: x) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Unknown) + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Null) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.jsonc similarity index 61% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.jsonc index 0cb854f57e3..5304308905a 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Given a unknown special form, suggest a correct one ["::kernel::special_form::nuwtype"] -//~^ERROR Did you mean to use 'newtype' instead? +//~^ ERROR cannot find value `nuwtype` in module `::kernel::special_form` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.stderr new file mode 100644 index 00000000000..51388251772 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form-typo.stderr @@ -0,0 +1,12 @@ +error[expander::item-not-found]: Item not found + ╭▸ +3 │ ["::kernel::special_form::nuwtype"] + │ ┬─────────── ━━━━━━━ cannot find value `nuwtype` in module `::kernel::special_form` + │ │ + │ looked in this module + ╰╴ +help: a similar item exists in this module + ╭╴ +3 - ["::kernel::special_form::nuwtype"] +3 + ["::kernel::special_form::newtype"] + ╰╴ \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.jsonc similarity index 61% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.jsonc index 1bdc92c6174..11021316943 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: Check if we error out if the special form is unknown ["::kernel::special_form::headpat"] -//~^ERROR This special form path is invalid +//~^ ERROR cannot find value `headpat` in module `::kernel::special_form` diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.stderr new file mode 100644 index 00000000000..cad1f12adaa --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/unknown-special-form.stderr @@ -0,0 +1,8 @@ +error[expander::item-not-found]: Item not found + ╭▸ +3 │ ["::kernel::special_form::headpat"] + │ ┬─────────── ━━━━━━━ cannot find value `headpat` in module `::kernel::special_form` + │ │ + │ looked in this module + │ + ╰ help: check the spelling and ensure the item is exported \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.jsonc similarity index 70% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.jsonc index 7d563102037..edd120d8d50 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.jsonc @@ -1,4 +1,4 @@ //@ run: fail //@ description: use/2 should error out ["::kernel::special_form::use", "::math", { "#tuple": ["pi"] }] -//~^ ERROR Add missing arguments +//~^ ERROR expected 3 arguments to `use`, found 2 diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.stderr new file mode 100644 index 00000000000..003e8dcda26 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-2.stderr @@ -0,0 +1,7 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ +3 │ ["::kernel::special_form::use", "::math", { "#tuple": ["pi"] }] + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected 3 arguments to `use`, found 2 + │ + ├ help: use `(use path imports body)` + ╰ note: the arguments are, in order: the module path to import from, the import specifier (`*`, a tuple of names, or a struct of aliases), and the body where the imports are in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.jsonc new file mode 100644 index 00000000000..71e318826f6 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: use/3 should compile to AST node +[ + "::kernel::special_form::use", + "::core::math", + { "#tuple": ["sqrt"] }, + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.stdout new file mode 100644 index 00000000000..2af30ba662d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-3.stdout @@ -0,0 +1,5 @@ +Expr#4294967040@21 + ExprKind (Literal) + LiteralExpr#4294967040@20 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.jsonc similarity index 80% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.jsonc index 4945cf3d9a8..940c2e43190 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.jsonc @@ -6,5 +6,5 @@ { "#tuple": ["pi"] }, "x", "y" - //~^ ERROR Remove this argument + //~^ ERROR unexpected argument ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.stderr new file mode 100644 index 00000000000..d3618b299f3 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-4.stderr @@ -0,0 +1,15 @@ +error[expander::invalid-argument-count]: Invalid argument count + ╭▸ + 3 │ ┏ [ + 4 │ ┃ "::kernel::special_form::use", + 5 │ ┃ "::math", + 6 │ ┃ { "#tuple": ["pi"] }, + 7 │ ┃ "x", + 8 │ ┃ "y" + │ ┃ ─ unexpected argument + 9 │ ┃ //~^ ERROR unexpected argument +10 │ ┃ ] + │ ┗━┛ expected 3 arguments to `use`, found 4 + │ + ├ help: use `(use path imports body)` + ╰ note: the arguments are, in order: the module path to import from, the import specifier (`*`, a tuple of names, or a struct of aliases), and the body where the imports are in scope \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.jsonc similarity index 56% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.jsonc index 24161d33a79..0b095d1b3f0 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.jsonc @@ -4,6 +4,7 @@ "::kernel::special_form::use", "::core", "::core::math::X", - //~^ ERROR Use a simple identifier here + //~^ ERROR expected `*`, a tuple of names, or a struct of aliases "x" + //~^ ERROR cannot find value `x` in this scope ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.stderr new file mode 100644 index 00000000000..08747c34738 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-ident.stderr @@ -0,0 +1,29 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +8 │ "x" + │ ━ cannot find value `x` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +8 - "x" +8 + "~" + ├╴ +8 - "x" +8 + "↑" + ├╴ +8 - "x" +8 + "√" + ├╴ +8 - "x" +8 + "∛" + ├╴ +8 │ "index" + ╰╴ ++++ + +error[expander::invalid-use-imports]: Invalid use imports + ╭▸ +6 │ "::core::math::X", + │ ━━━━━━━━━━━━━━━ expected `*`, a tuple of names, or a struct of aliases + │ + ╰ help: use `*` to import everything, `(a, b)` to import specific names, or `(a: alias, b: _)` to import with aliases \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.jsonc similarity index 52% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.jsonc index 3abb8522d15..c94aaf3a59f 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.jsonc @@ -4,6 +4,7 @@ "::kernel::special_form::use", "::math", "X", - //~^ ERROR Replace with a valid import expression + //~^ ERROR expected `*`, a tuple of names, or a struct of aliases "x" + //~^ ERROR cannot find value `x` in this scope ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.stderr new file mode 100644 index 00000000000..fb876c95f42 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob-not-star.stderr @@ -0,0 +1,29 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +8 │ "x" + │ ━ cannot find value `x` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +8 - "x" +8 + "~" + ├╴ +8 - "x" +8 + "↑" + ├╴ +8 - "x" +8 + "√" + ├╴ +8 - "x" +8 + "∛" + ├╴ +8 │ "index" + ╰╴ ++++ + +error[expander::invalid-use-imports]: Invalid use imports + ╭▸ +6 │ "X", + │ ━ expected `*`, a tuple of names, or a struct of aliases + │ + ╰ help: use `*` to import everything, `(a, b)` to import specific names, or `(a: alias, b: _)` to import with aliases \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.jsonc new file mode 100644 index 00000000000..ce815af69dd --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: glob pattern should compile to AST node +["::kernel::special_form::use", "::core::math", "*", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.stdout new file mode 100644 index 00000000000..a54e23aba41 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-glob.stdout @@ -0,0 +1,5 @@ +Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.jsonc similarity index 54% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.jsonc index 912a728d5d4..1ce484a6dee 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.jsonc @@ -4,6 +4,7 @@ "::kernel::special_form::use", "::math", { "#literal": 1 }, - //~^ ERROR Replace with a valid import expression + //~^ ERROR expected `*`, a tuple of names, or a struct of aliases "x" + //~^ ERROR cannot find value `x` in this scope ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.stderr new file mode 100644 index 00000000000..e53366834cd --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-literal.stderr @@ -0,0 +1,29 @@ +error[expander::unresolved-variable]: Unresolved variable + ╭▸ +8 │ "x" + │ ━ cannot find value `x` in this scope + │ + ╰ note: this could be a typo, a name used outside its scope, or a missing declaration; if it is a function or type from another module, you may need to import it first +help: a similar imported name exists + ╭╴ +8 - "x" +8 + "~" + ├╴ +8 - "x" +8 + "↑" + ├╴ +8 - "x" +8 + "√" + ├╴ +8 - "x" +8 + "∛" + ├╴ +8 │ "index" + ╰╴ ++++ + +error[expander::invalid-use-imports]: Invalid use imports + ╭▸ +6 │ { "#literal": 1 }, + │ ━━━━━━━━━━━━━━━━━ expected `*`, a tuple of names, or a struct of aliases + │ + ╰ help: use `*` to import everything, `(a, b)` to import specific names, or `(a: alias, b: _)` to import with aliases \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.jsonc index e9d9af0fb77..57fc732f27f 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.jsonc @@ -2,12 +2,12 @@ //@ description: any value that isn't an identifier should fail [ "::kernel::special_form::use", - "::math", + "::core::math", { "#struct": { - "x": { "#literal": 1 } - //~^ ERROR Replace with a valid import expression + "sqrt": { "#literal": 1 } + //~^ ERROR expected `_` or a simple identifier as the alias } }, - "x" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.stderr new file mode 100644 index 00000000000..7caa8f9c285 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-literal.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-use-alias]: Invalid use alias + ╭▸ +8 │ "sqrt": { "#literal": 1 } + │ ━━━━━━━━━━━━━━━━━ expected `_` or a simple identifier as the alias + │ + ╰ help: use `_` to keep the original name or a simple identifier to rename: `(name: alias)` or `(name: _)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.jsonc similarity index 51% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.jsonc index c4babb43fa9..7d70a1a686d 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.jsonc @@ -2,12 +2,12 @@ //@ description: path values that aren't identifiers should fail [ "::kernel::special_form::use", - "::core", + "::core::math", { "#struct": { - "x": "::core::math::sub" - //~^ ERROR Use a simple identifier here + "add": "::core::math::sub" + //~^ ERROR expected `_` or a simple identifier as the alias } }, - "x" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.stderr new file mode 100644 index 00000000000..88d0009e18f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-path.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-use-alias]: Invalid use alias + ╭▸ +8 │ "add": "::core::math::sub" + │ ━━━━━━━━━━━━━━━━━ expected `_` or a simple identifier as the alias + │ + ╰ help: use `_` to keep the original name or a simple identifier to rename: `(name: alias)` or `(name: _)` \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.jsonc similarity index 50% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.jsonc index 4b56034bfd1..835fe191d45 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.jsonc @@ -2,11 +2,11 @@ //@ description: struct that are annotated with a type should fail [ "::kernel::special_form::use", - "::math", + "::core::math", { - "#struct": { "x": "x" }, + "#struct": { "sqrt": "sqrt" }, "#type": "X" - //~^ ERROR Remove this type annotation + //~^ ERROR type annotations are not allowed on the import list }, - "x" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.stderr new file mode 100644 index 00000000000..9da4d85a6d2 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct-type.stderr @@ -0,0 +1,6 @@ +error[expander::use-imports-type-annotation]: Type annotation on use imports + ╭▸ +8 │ "#type": "X" + │ ━ type annotations are not allowed on the import list + │ + ╰ help: remove the type annotation; the import list declares which names to bring into scope, not a typed value \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.jsonc similarity index 56% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.jsonc index e79c77b13c5..af2735df683 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.jsonc @@ -2,7 +2,7 @@ //@ description: struct pattern should compile to AST node [ "::kernel::special_form::use", - "::math", - { "#struct": { "x": "y", "z": "_" } }, - "x" + "::core::math", + { "#struct": { "sqrt": "y", "cbrt": "_" } }, + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.stdout new file mode 100644 index 00000000000..1f98ff9062f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-struct.stdout @@ -0,0 +1,5 @@ +Expr#4294967040@29 + ExprKind (Literal) + LiteralExpr#4294967040@28 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.jsonc similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.jsonc index 6575f11162e..057d558d1ef 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.jsonc @@ -4,6 +4,6 @@ "::kernel::special_form::use", "::math", { "#tuple": [{ "#literal": 1 }] }, - //~^ ERROR Replace with a valid import expression - "x" + //~^ ERROR expected a simple name to import + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.stderr new file mode 100644 index 00000000000..77e55111e68 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-literal.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-use-import-binding]: Invalid use import binding + ╭▸ +6 │ { "#tuple": [{ "#literal": 1 }] }, + │ ━━━━━━━━━━━━━━━━━ expected a simple name to import + │ + ╰ help: each import must be a plain identifier like `foo`, not a qualified path or expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.jsonc similarity index 71% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.jsonc index 126549e0c3b..8bf923db8f2 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.jsonc @@ -4,6 +4,6 @@ "::kernel::special_form::use", "::core", { "#tuple": ["::core::math::sub"] }, - //~^ ERROR Use a simple identifier here - "x" + //~^ ERROR expected a simple name to import + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.stderr new file mode 100644 index 00000000000..0b9e0e199bb --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-path.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-use-import-binding]: Invalid use import binding + ╭▸ +6 │ { "#tuple": ["::core::math::sub"] }, + │ ━━━━━━━━━━━━━━━━━ expected a simple name to import + │ + ╰ help: each import must be a plain identifier like `foo`, not a qualified path or expression \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.jsonc similarity index 58% rename from libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.jsonc index 2d0da5307af..399cae67206 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.jsonc @@ -4,6 +4,7 @@ "::kernel::special_form::use", "::core", { "#tuple": ["::core::math::sub"], "#type": "Number" }, - //~^ ERROR Remove this type annotation - "x" + //~^ ERROR expected a simple name to import + //~| ERROR type annotations are not allowed on the import list + { "#literal": 0 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.stderr new file mode 100644 index 00000000000..9d46f19850f --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple-type.stderr @@ -0,0 +1,13 @@ +error[expander::invalid-use-import-binding]: Invalid use import binding + ╭▸ +6 │ { "#tuple": ["::core::math::sub"], "#type": "Number" }, + │ ━━━━━━━━━━━━━━━━━ expected a simple name to import + │ + ╰ help: each import must be a plain identifier like `foo`, not a qualified path or expression + +error[expander::use-imports-type-annotation]: Type annotation on use imports + ╭▸ +6 │ { "#tuple": ["::core::math::sub"], "#type": "Number" }, + │ ━━━━━━ type annotations are not allowed on the import list + │ + ╰ help: remove the type annotation; the import list declares which names to bring into scope, not a typed value \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.jsonc new file mode 100644 index 00000000000..aef68764c57 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: tuple pattern should compile to AST node +[ + "::kernel::special_form::use", + "::core::math", + { "#tuple": ["sqrt", "cbrt"] }, + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.stdout new file mode 100644 index 00000000000..5b305a51cc0 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/special-form-expander/use-tuple.stdout @@ -0,0 +1,5 @@ +Expr#4294967040@25 + ExprKind (Literal) + LiteralExpr#4294967040@24 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.jsonc new file mode 100644 index 00000000000..35b65805374 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: labeled arguments are not allowed in type constructor calls +[ + "::kernel::special_form::type", + "Foo", + ["|", "Integer", { ":extra": "String" }], + //~^ ERROR labeled arguments are not allowed in type constructor calls + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.stderr new file mode 100644 index 00000000000..2226a15b9c1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-labeled-argument.stderr @@ -0,0 +1,7 @@ +error[expander::labeled-arguments-not-supported]: Labeled arguments not supported + ╭▸ +6 │ ["|", "Integer", { ":extra": "String" }], + │ ━━━━━━━━━━━━━━━━━━ labeled arguments are not allowed in type constructor calls + │ + ├ help: remove the labels and write the operand types positionally, for example `(| Int String)` + ╰ note: type constructors such as `|` (union) and `&` (intersection) take positional type operands \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.jsonc new file mode 100644 index 00000000000..616bd929caf --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: calling a type intrinsic that is not a type constructor (Union/Intersection) should error +[ + "::kernel::special_form::type", + "Foo", + ["::kernel::type::List", "Integer"], + //~^ ERROR `::kernel::type::List` is a type, not a type constructor + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.stderr new file mode 100644 index 00000000000..578f82b5847 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-call-not-constructor.stderr @@ -0,0 +1,10 @@ +error[expander::invalid-type-constructor-call]: Invalid type constructor call + ╭▸ +6 │ ["::kernel::type::List", "Integer"], + │ ┬─┯━━━━━━━━━━━━━━━━━━━───────────── + │ │ │ + │ │ `::kernel::type::List` is a type, not a type constructor + │ call syntax is only valid for type constructors like `|` and `&` + │ + ├ help: use `::kernel::type::List` directly as a type path, or write `::kernel::type::List<...>` for generic arguments + ╰ note: only `|` (union) and `&` (intersection) can be called as type constructors in type position \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.jsonc new file mode 100644 index 00000000000..3cd8a22cf1a --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: duplicate generic constraint names using the constraint syntax should error +[ + "::kernel::special_form::type", + "Foo", + //~^ ERROR duplicate generic parameter `T` + { "#struct": { "foo": "T" } }, + "_" +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.stderr new file mode 100644 index 00000000000..1b8bef0a877 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-generic-constraint-duplicate.stderr @@ -0,0 +1,8 @@ +error[expander::duplicate-generic-constraint]: Duplicate generic constraint + ╭▸ +5 │ "Foo", + │ ┬ ━ duplicate generic parameter `T` + │ │ + │ `T` was first declared here + │ + ╰ help: remove the duplicate declaration or use a different name \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.jsonc new file mode 100644 index 00000000000..e685883183e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: rebinding Union intrinsic should be swallowed (body returned directly) +[ + "::kernel::special_form::type", + "MyUnion", + "|", + ["type", "Foo", ["MyUnion", "Integer", "String"], { "#literal": 0 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.stdout b/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.stdout new file mode 100644 index 00000000000..9799e430eda --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/type-intrinsic-swallow.stdout @@ -0,0 +1,23 @@ +Expr#4294967040@39 + ExprKind (Type) + TypeExpr#4294967040@39 (name: Foo) + Type#4294967040@36 + TypeKind (Union) + UnionType#4294967040@36 + Type#4294967040@31 + TypeKind (Path) + Path#4294967040@31 (rooted: true) + PathSegment#4294967040@30 (name: kernel) + PathSegment#4294967040@30 (name: type) + PathSegment#4294967040@30 (name: Integer) + Type#4294967040@35 + TypeKind (Path) + Path#4294967040@35 (rooted: true) + PathSegment#4294967040@34 (name: kernel) + PathSegment#4294967040@34 (name: type) + PathSegment#4294967040@34 (name: String) + Expr#4294967040@38 + ExprKind (Literal) + LiteralExpr#4294967040@37 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.jsonc new file mode 100644 index 00000000000..5d895613f54 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: duplicate names in use bindings should error +[ + "::kernel::special_form::use", + "::core::math", + { "#tuple": ["add", "add"] }, + //~^ ERROR duplicate import binding `add` + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.stderr new file mode 100644 index 00000000000..dbd5f119fb9 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/use-duplicate-binding.stderr @@ -0,0 +1,8 @@ +error[expander::duplicate-use-binding]: Duplicate use binding + ╭▸ +6 │ { "#tuple": ["add", "add"] }, + │ ┬── ━━━ duplicate import binding `add` + │ │ + │ `add` was first imported here + │ + ╰ help: remove the duplicate or use an alias to import under a different name \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.jsonc new file mode 100644 index 00000000000..6d4c75f8330 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.jsonc @@ -0,0 +1,9 @@ +//@ run: fail +//@ description: the use path must be a path expression, not a literal +[ + "::kernel::special_form::use", + { "#literal": 1 }, + //~^ ERROR expected a module path + { "#tuple": ["add"] }, + { "#literal": 0 } +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.stderr b/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.stderr new file mode 100644 index 00000000000..43689fe0d12 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/expander/use-invalid-path.stderr @@ -0,0 +1,6 @@ +error[expander::invalid-use-path]: Invalid use path + ╭▸ +5 │ { "#literal": 1 }, + │ ━━━━━━━━━━━━━━━━━ expected a module path + │ + ╰ help: the first argument to `use` must be a path like `core::math` identifying the module to import from \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/.spec.toml new file mode 100644 index 00000000000..ddbe402a1eb --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/name-mangler" diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.jsonc new file mode 100644 index 00000000000..f767ff3a251 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.jsonc @@ -0,0 +1,8 @@ +//@ run: pass +//@ description: Absolute paths should stay unchanged. +[ + "let", + "add", + { "#literal": 2 }, + ["::core::math::add", { "#literal": 1 }, { "#literal": 3 }] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.stdout new file mode 100644 index 00000000000..b45530ab13d --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/absolute-path.stdout @@ -0,0 +1,29 @@ +Expr#4294967040@23 + ExprKind (Let) + LetExpr#4294967040@23 (name: add:0) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (2) + Expr#4294967040@22 + ExprKind (Call) + CallExpr#4294967040@22 + Expr#4294967040@17 + ExprKind (Path) + Path#4294967040@17 (rooted: true) + PathSegment#4294967040@12 (name: core) + PathSegment#4294967040@14 (name: math) + PathSegment#4294967040@16 (name: add) + Argument#4294967040@19 + Expr#4294967040@19 + ExprKind (Literal) + LiteralExpr#4294967040@18 + Primitive (Integer) + Integer (1) + Argument#4294967040@21 + Expr#4294967040@21 + ExprKind (Literal) + LiteralExpr#4294967040@20 + Primitive (Integer) + Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.jsonc similarity index 76% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.jsonc index 535a63db170..5178b75d3af 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.jsonc @@ -4,5 +4,5 @@ "if", { "#literal": true }, ["let", "a", { "#literal": 2 }, { "#literal": 3 }], - "a" + ["let", "a", { "#literal": 3 }, { "#literal": 2 }] ] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.stdout similarity index 54% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.stdout index f9b322a533c..0b952ea35fc 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/diverging.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/diverging.stdout @@ -1,6 +1,6 @@ -Expr#4294967040@23 +Expr#4294967040@32 ExprKind (If) - IfExpr#4294967040@23 + IfExpr#4294967040@32 Expr#4294967040@5 ExprKind (Literal) LiteralExpr#4294967040@4 @@ -18,7 +18,16 @@ Expr#4294967040@23 LiteralExpr#4294967040@16 Primitive (Integer) Integer (3) - Expr#4294967040@22 - ExprKind (Path) - Path#4294967040@22 (rooted: false) - PathSegment#4294967040@21 (name: a) + Expr#4294967040@31 + ExprKind (Let) + LetExpr#4294967040@31 (name: a:1) + Expr#4294967040@28 + ExprKind (Literal) + LiteralExpr#4294967040@27 + Primitive (Integer) + Integer (3) + Expr#4294967040@30 + ExprKind (Literal) + LiteralExpr#4294967040@29 + Primitive (Integer) + Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-args.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-args.jsonc new file mode 100644 index 00000000000..a98f4ad34eb --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-args.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: test that arguments are mangled correctly +["fn", { "#tuple": ["T"] }, { "#struct": { "a": "T" } }, "_", ["as", "a", "T"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-args.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-args.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-args.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-args.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-args.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-args.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-args.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-args.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-args.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-args.stdout new file mode 100644 index 00000000000..f1b625bb0f6 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-args.stdout @@ -0,0 +1,24 @@ +Expr#4294967040@34 + ExprKind (Closure) + ClosureExpr#4294967040@34 + ClosureSignature#4294967040@34 + Generics#4294967040@8 + GenericParam#4294967040@7 (name: T:0) + ClosureParam#4294967040@16 (name: a:0) + Type#4294967040@15 + TypeKind (Path) + Path#4294967040@15 (rooted: false) + PathSegment#4294967040@14 (name: T:0) + Type#4294967040@20 + TypeKind (Infer) + Expr#4294967040@33 + ExprKind (As) + AsExpr#4294967040@33 + Expr#4294967040@28 + ExprKind (Path) + Path#4294967040@28 (rooted: false) + PathSegment#4294967040@27 (name: a:0) + Type#4294967040@32 + TypeKind (Path) + Path#4294967040@32 (rooted: false) + PathSegment#4294967040@31 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-ret.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-ret.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-ret.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-ret.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-ret.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-ret.stdout similarity index 80% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-ret.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-ret.stdout index dcabca45e00..840766cecb2 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types-of-ret.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types-of-ret.stdout @@ -7,7 +7,9 @@ Expr#4294967040@27 ClosureParam#4294967040@16 (name: a:0) Type#4294967040@15 TypeKind (Path) - Path#4294967040@15 (rooted: false) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) PathSegment#4294967040@14 (name: Integer) Type#4294967040@22 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.jsonc similarity index 76% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.jsonc index ad841f14f30..58736f4c4bd 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.jsonc @@ -5,5 +5,5 @@ { "#tuple": ["T", "U"] }, { "#struct": {} }, "_", - ["as", ["as", "T", "T"], "U"] + ["as", ["as", { "#literal": 0 }, "T"], "U"] ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.stdout new file mode 100644 index 00000000000..c98ed523ce1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/fn-types.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@38 + ExprKind (Closure) + ClosureExpr#4294967040@38 + ClosureSignature#4294967040@38 + Generics#4294967040@12 + GenericParam#4294967040@7 (name: T:0) + GenericParam#4294967040@11 (name: U:0) + Type#4294967040@17 + TypeKind (Infer) + Expr#4294967040@37 + ExprKind (As) + AsExpr#4294967040@37 + Expr#4294967040@32 + ExprKind (As) + AsExpr#4294967040@32 + Expr#4294967040@27 + ExprKind (Literal) + LiteralExpr#4294967040@26 + Primitive (Integer) + Integer (0) + Type#4294967040@31 + TypeKind (Path) + Path#4294967040@31 (rooted: false) + PathSegment#4294967040@30 (name: T:0) + Type#4294967040@36 + TypeKind (Path) + Path#4294967040@36 (rooted: false) + PathSegment#4294967040@35 (name: U:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/let.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/let.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/let.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/let.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/let.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/let.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/let.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/let.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.jsonc similarity index 85% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.jsonc index f4997dcbb4d..67c54f9f802 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.jsonc @@ -2,7 +2,7 @@ //@ description: mangle the name of the generics [ "newtype", - "Foo", + "Foo", { "#struct": { "foo": "T", "bar": "U", "baz": "V", "inner": "Foo" } }, diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.stdout similarity index 90% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.stdout index 8f80baff772..89a2500a7d9 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype-generics.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype-generics.stdout @@ -4,17 +4,19 @@ Expr#4294967040@70 GenericConstraint#4294967040@10 (name: T:0) Type#4294967040@9 TypeKind (Path) - Path#4294967040@9 (rooted: false) - PathSegment#4294967040@8 (name: Bar) + Path#4294967040@9 (rooted: true) + PathSegment#4294967040@8 (name: kernel) + PathSegment#4294967040@8 (name: type) + PathSegment#4294967040@8 (name: Number) GenericConstraint#4294967040@15 (name: U:0) Type#4294967040@14 TypeKind (Path) Path#4294967040@14 (rooted: false) PathSegment#4294967040@13 (name: V:0) GenericConstraint#4294967040@20 (name: V:0) - Type#4294967040@66 + Type#4294967040@67 TypeKind (Struct) - StructType#4294967040@66 + StructType#4294967040@67 StructField#4294967040@29 (name: foo) Type#4294967040@28 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.jsonc new file mode 100644 index 00000000000..e4116437210 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.jsonc @@ -0,0 +1,3 @@ +//@ run: pass +//@ description: mangle the name of a simple type expression +["let", "T", { "#literal": 1 }, ["newtype", "T", "Number", ["as", "T", "T"]]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.stdout new file mode 100644 index 00000000000..1ca18f90635 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/newtype.stdout @@ -0,0 +1,28 @@ +Expr#4294967040@36 + ExprKind (Let) + LetExpr#4294967040@36 (name: T:0) + Expr#4294967040@9 + ExprKind (Literal) + LiteralExpr#4294967040@8 + Primitive (Integer) + Integer (1) + Expr#4294967040@35 + ExprKind (NewType) + NewTypeExpr#4294967040@35 (name: T:1) + Type#4294967040@21 + TypeKind (Path) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: kernel) + PathSegment#4294967040@20 (name: type) + PathSegment#4294967040@20 (name: Number) + Expr#4294967040@34 + ExprKind (As) + AsExpr#4294967040@34 + Expr#4294967040@29 + ExprKind (Path) + Path#4294967040@29 (rooted: false) + PathSegment#4294967040@28 (name: T:1) + Type#4294967040@33 + TypeKind (Path) + Path#4294967040@33 (rooted: false) + PathSegment#4294967040@32 (name: T:1) diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.jsonc new file mode 100644 index 00000000000..6300fc472a6 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.jsonc @@ -0,0 +1,18 @@ +//@ run: pass +//@ description: Make sure that if we have an "onion" of bindings, the outer binding is preserved on exit +[ + "type", + "T", + "Number", + [ + "*", + [ + "fn", + { "#tuple": ["T", "U"] }, + { "#struct": {} }, + "_", + ["as", { "#literal": 0 }, "T"] + ], + ["as", { "#literal": 0 }, "T"] + ] +] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.stdout new file mode 100644 index 00000000000..b4c5b0651ba --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/onion.stdout @@ -0,0 +1,53 @@ +Expr#4294967040@58 + ExprKind (Type) + TypeExpr#4294967040@58 (name: T:0) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Number) + Expr#4294967040@57 + ExprKind (Call) + CallExpr#4294967040@57 + Expr#4294967040@15 + ExprKind (Path) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: core) + PathSegment#4294967040@14 (name: math) + PathSegment#4294967040@14 (name: mul) + Argument#4294967040@45 + Expr#4294967040@45 + ExprKind (Closure) + ClosureExpr#4294967040@45 + ClosureSignature#4294967040@45 + Generics#4294967040@28 + GenericParam#4294967040@23 (name: T:1) + GenericParam#4294967040@27 (name: U:0) + Type#4294967040@33 + TypeKind (Infer) + Expr#4294967040@44 + ExprKind (As) + AsExpr#4294967040@44 + Expr#4294967040@39 + ExprKind (Literal) + LiteralExpr#4294967040@38 + Primitive (Integer) + Integer (0) + Type#4294967040@43 + TypeKind (Path) + Path#4294967040@43 (rooted: false) + PathSegment#4294967040@42 (name: T:1) + Argument#4294967040@56 + Expr#4294967040@56 + ExprKind (As) + AsExpr#4294967040@56 + Expr#4294967040@51 + ExprKind (Literal) + LiteralExpr#4294967040@50 + Primitive (Integer) + Integer (0) + Type#4294967040@55 + TypeKind (Path) + Path#4294967040@55 (rooted: false) + PathSegment#4294967040@54 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite-body.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite-body.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite-body.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite-body.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite-body.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite-body.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite-body.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite-body.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/overwrite.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/overwrite.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.jsonc similarity index 58% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.jsonc index 824cae4c004..bd10868151e 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: paths with arguments should change -["let", "a", { "#literal": 2 }, "a"] +["let", "a", { "#literal": 2 }, "a"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.stdout similarity index 68% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.stdout index f60fbbedd2a..5ac40c2846b 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path-arguments.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path-arguments.stdout @@ -14,5 +14,7 @@ Expr#4294967040@19 GenericArgument#4294967040@16 Type#4294967040@15 TypeKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: T) + Path#4294967040@15 (rooted: true) + PathSegment#4294967040@14 (name: kernel) + PathSegment#4294967040@14 (name: type) + PathSegment#4294967040@14 (name: Integer) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.jsonc similarity index 57% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.jsonc index 53adbf91d13..459f3223322 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: non-rooted paths should change. -["let", "a", { "#literal": 2 }, "a::b"] +["use", "core::math", "*", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.stdout new file mode 100644 index 00000000000..566a6798742 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/path.stdout @@ -0,0 +1,5 @@ +Expr#4294967040@15 + ExprKind (Literal) + LiteralExpr#4294967040@14 + Primitive (Integer) + Integer (0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/scopes-no-include.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/scopes-no-include.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/scopes-no-include.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/scopes-no-include.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/scopes-no-include.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/scopes-no-include.stdout similarity index 84% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/scopes-no-include.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/scopes-no-include.stdout index 8c6ee37408e..fbaeb372930 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/scopes-no-include.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/scopes-no-include.stdout @@ -11,7 +11,9 @@ Expr#4294967040@36 TypeExpr#4294967040@35 (name: a:1) Type#4294967040@21 TypeKind (Path) - Path#4294967040@21 (rooted: false) + Path#4294967040@21 (rooted: true) + PathSegment#4294967040@20 (name: kernel) + PathSegment#4294967040@20 (name: type) PathSegment#4294967040@20 (name: Integer) Expr#4294967040@34 ExprKind (As) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/simple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/simple.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/simple.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/simple.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/simple.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/simple.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/simple.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/simple.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.jsonc similarity index 54% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.jsonc index 334703cf932..1e8e25ae447 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: mangle the name of the generics with constraints -["type", "Foo", { "#struct": { "foo": "Bar" } }, "_"] +["type", "Foo", { "#struct": { "foo": "Bar" } }, "_"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.stdout similarity index 64% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.stdout index 36a975f6701..49e553d4fb6 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-constraints.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-constraints.stdout @@ -4,11 +4,13 @@ Expr#4294967040@24 GenericConstraint#4294967040@10 (name: Bar:0) Type#4294967040@9 TypeKind (Path) - Path#4294967040@9 (rooted: false) - PathSegment#4294967040@8 (name: Baz) - Type#4294967040@20 + Path#4294967040@9 (rooted: true) + PathSegment#4294967040@8 (name: kernel) + PathSegment#4294967040@8 (name: type) + PathSegment#4294967040@8 (name: Integer) + Type#4294967040@21 TypeKind (Struct) - StructType#4294967040@20 + StructType#4294967040@21 StructField#4294967040@19 (name: foo) Type#4294967040@18 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-interdependent-generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-interdependent-generics.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-interdependent-generics.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-interdependent-generics.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-interdependent-generics.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-interdependent-generics.stdout similarity index 93% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-interdependent-generics.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-interdependent-generics.stdout index 7e85bb78fc3..7d41dc29bac 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-interdependent-generics.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-interdependent-generics.stdout @@ -7,9 +7,9 @@ Expr#4294967040@36 Path#4294967040@9 (rooted: false) PathSegment#4294967040@8 (name: U:0) GenericConstraint#4294967040@15 (name: U:0) - Type#4294967040@32 + Type#4294967040@33 TypeKind (Struct) - StructType#4294967040@32 + StructType#4294967040@33 StructField#4294967040@24 (name: foo) Type#4294967040@23 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-nested.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-nested.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-nested.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-nested.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-nested.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-nested.stdout similarity index 79% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-nested.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-nested.stdout index 927d52eb06a..9f7d3b0df70 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-nested.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-nested.stdout @@ -3,7 +3,9 @@ Expr#4294967040@37 TypeExpr#4294967040@37 (name: Foo:0) Type#4294967040@11 TypeKind (Path) - Path#4294967040@11 (rooted: false) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) PathSegment#4294967040@10 (name: String) Expr#4294967040@36 ExprKind (Type) @@ -13,9 +15,9 @@ Expr#4294967040@37 TypeKind (Path) Path#4294967040@21 (rooted: false) PathSegment#4294967040@20 (name: Foo:0) - Type#4294967040@32 + Type#4294967040@33 TypeKind (Struct) - StructType#4294967040@32 + StructType#4294967040@33 StructField#4294967040@31 (name: foo) Type#4294967040@30 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-recursive.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-recursive.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-recursive.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-recursive.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-recursive.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-recursive.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics-recursive.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics-recursive.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics.stdout similarity index 87% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics.stdout rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics.stdout index 8d630b9cb1f..e8c9e637af3 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type-generics.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type-generics.stdout @@ -2,9 +2,9 @@ Expr#4294967040@24 ExprKind (Type) TypeExpr#4294967040@24 (name: Foo:0) GenericConstraint#4294967040@10 (name: Bar:0) - Type#4294967040@20 + Type#4294967040@21 TypeKind (Struct) - StructType#4294967040@20 + StructType#4294967040@21 StructField#4294967040@19 (name: foo) Type#4294967040@18 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.jsonc similarity index 70% rename from libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.jsonc index 6fb18dab03e..5d7e7c1f533 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.jsonc @@ -1,4 +1,4 @@ //@ run: pass //@ description: mangle the name of a simple type expression // We only need to verify that mangling works correctly, as -["type", "T", "X", ["as", "T", "T"]] +["type", "T", "Integer", ["as", { "#literal": 0 }, "T"]] diff --git a/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.stdout b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.stdout new file mode 100644 index 00000000000..4e713f66ac7 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/name-mangler/type.stdout @@ -0,0 +1,21 @@ +Expr#4294967040@23 + ExprKind (Type) + TypeExpr#4294967040@23 (name: T:0) + Type#4294967040@11 + TypeKind (Path) + Path#4294967040@11 (rooted: true) + PathSegment#4294967040@10 (name: kernel) + PathSegment#4294967040@10 (name: type) + PathSegment#4294967040@10 (name: Integer) + Expr#4294967040@22 + ExprKind (As) + AsExpr#4294967040@22 + Expr#4294967040@17 + ExprKind (Literal) + LiteralExpr#4294967040@16 + Primitive (Integer) + Integer (0) + Type#4294967040@21 + TypeKind (Path) + Path#4294967040@21 (rooted: false) + PathSegment#4294967040@20 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/.spec.toml new file mode 100644 index 00000000000..57c7233188e --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/node-renumberer" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/basic.jsonc b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/basic.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/basic.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/basic.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/basic.stdout b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/basic.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/basic.stdout rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/basic.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/diverging.jsonc b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/diverging.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/diverging.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/diverging.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/diverging.stdout b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/diverging.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/diverging.stdout rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/diverging.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/nested.jsonc b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/nested.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/nested.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/nested.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/nested.stdout b/libs/@local/hashql/ast/tests/ui/lower/node-renumberer/nested.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/nested.stdout rename to libs/@local/hashql/ast/tests/ui/lower/node-renumberer/nested.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lower/sanitizer/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/.spec.toml new file mode 100644 index 00000000000..668705131d4 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/sanitizer" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.jsonc b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.jsonc similarity index 90% rename from libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.jsonc index a8d5cfa8964..226f69268f1 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.jsonc @@ -1,7 +1,7 @@ //@ run: fail //@ description: Generic constraints with bounds should be sanitized and error out [ - "add", + "`+`", //~^ ERROR Remove this constraint from 'T' { "#literal": 1 }, { "#literal": 2 } diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.stderr b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.stderr similarity index 95% rename from libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.stderr rename to libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.stderr index bf190e01187..5eafebf1e1d 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/constraint.stderr +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/constraint.stderr @@ -1,6 +1,6 @@ error[sanitizer::invalid-generic-constraint]: Invalid generic constraint ╭▸ -4 │ "add", +4 │ "`+`", │ ━━━━━━━━━ Remove this constraint from 'T' │ ├ help: Generic constraints (like 'T: Bound') are not allowed in this context. Use just the parameter name without bounds: 'T'. For example, change 'T: Clone' to just 'T'. diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.jsonc b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.jsonc similarity index 70% rename from libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.jsonc index 177080f18ea..230501b1eaa 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.jsonc @@ -4,6 +4,6 @@ "type", "A", "::kernel::special_form::let", - //~^ ERROR Special form cannot be used as a type + //~^ ERROR cannot find type `let` in module `::kernel::special_form` "_" ] diff --git a/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.stderr b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.stderr new file mode 100644 index 00000000000..764bad4cb13 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-type-position.stderr @@ -0,0 +1,8 @@ +error[expander::item-not-found]: Item not found + ╭▸ +6 │ "::kernel::special_form::let", + │ ┬─────────── ━━━ cannot find type `let` in module `::kernel::special_form` + │ │ + │ looked in this module + │ + ╰ help: `let` exists as a value, not type \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-value.jsonc b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-value.jsonc similarity index 87% rename from libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-value.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-value.jsonc index b01ee5136a5..6e76644c862 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-value.jsonc +++ b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-value.jsonc @@ -1,8 +1,8 @@ //@ run: fail //@ description: Ensure that special forms in invalid value positions are sanitized and error out [ - "add", + "`+`", "::kernel::special_form::let", //~^ ERROR Special form cannot be used as a value - "a" + { "#literal": 1 } ] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-value.stderr b/libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-value.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-value.stderr rename to libs/@local/hashql/ast/tests/ui/lower/sanitizer/special-form-value.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lower/type-extractor/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/.spec.toml new file mode 100644 index 00000000000..5ff02c744b1 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/type-extractor" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-generics.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-generics.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-generics.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-generics.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-generics.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-generics.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-generics.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-generics.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-unused-generic.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-unused-generic.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-unused-generic.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-unused-generic.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-unused-generic.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-unused-generic.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure-unused-generic.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure-unused-generic.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/closure.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/closure.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/complex-types.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/complex-types.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/complex-types.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/complex-types.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/complex-types.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/complex-types.stdout similarity index 96% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/complex-types.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/complex-types.stdout index fb0021b0d74..99c40dd6ab2 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/complex-types.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/complex-types.stdout @@ -16,9 +16,9 @@ Expr#0@38 LiteralExpr#9@31 Primitive (Integer) Integer (30) - Type#10@22 + Type#10@23 TypeKind (Struct) - StructType#11@22 + StructType#11@23 StructField#12@14 (name: name) Type#13@13 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/.spec.toml b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/.spec.toml new file mode 100644 index 00000000000..c8863299236 --- /dev/null +++ b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/.spec.toml @@ -0,0 +1 @@ +suite = "ast/lower/type-definition-extractor" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-invalid-location.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-invalid-location.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-invalid-location.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-invalid-location.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-invalid-location.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-invalid-location.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-invalid-location.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-invalid-location.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-parsing.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-parsing.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-parsing.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-parsing.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-parsing.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-parsing.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/constraints-parsing.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/constraints-parsing.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-guarded.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-guarded.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-guarded.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-guarded.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-guarded.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-guarded.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-guarded.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-guarded.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-union.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-union.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-union.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-union.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-union.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-union.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/contractive-union.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/contractive-union.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch-too-many.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch-too-many.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch-too-many.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch-too-many.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch-too-many.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch-too-many.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch-too-many.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch-too-many.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/dict-parameter-count-mismatch.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/dict-parameter-count-mismatch.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/duplicate-fields.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/duplicate-fields.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/duplicate-fields.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/duplicate-fields.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/duplicate-fields.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/duplicate-fields.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/duplicate-fields.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/duplicate-fields.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/env-type-interning.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/env-type-interning.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/env-type-interning.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/env-type-interning.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/env-type-interning.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/env-type-interning.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/env-type-interning.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/env-type-interning.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-apply-params.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-apply-params.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-apply-params.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-apply-params.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-apply-params.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-apply-params.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-apply-params.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-apply-params.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-param-count-mismatch.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-param-count-mismatch.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-param-count-mismatch.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-param-count-mismatch.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-param-count-mismatch.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-param-count-mismatch.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-param-count-mismatch.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-param-count-mismatch.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-with-params.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-with-params.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-with-params.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-with-params.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-with-params.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-with-params.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/generics-with-params.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/generics-with-params.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-not-enough.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-not-enough.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-not-enough.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-not-enough.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-not-enough.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-not-enough.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-not-enough.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-not-enough.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-too-many.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-too-many.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-too-many.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-too-many.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-too-many.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-too-many.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/global-generic-constraint-too-many.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/global-generic-constraint-too-many.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-nominal.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-nominal.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-nominal.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-nominal.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-nominal.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-nominal.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-nominal.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-nominal.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-structural.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-structural.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-structural.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-structural.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-structural.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-structural.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/identity-structural.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/identity-structural.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/infer-with-arguments.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/infer-with-arguments.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/infer-with-arguments.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/infer-with-arguments.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/infer-with-arguments.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/infer-with-arguments.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/infer-with-arguments.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/infer-with-arguments.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-dict-translation.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-dict-translation.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-dict-translation.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-dict-translation.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-dict-translation.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-dict-translation.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-dict-translation.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-dict-translation.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-list-translation.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-list-translation.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-list-translation.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-list-translation.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-list-translation.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-list-translation.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/intrinsic-list-translation.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/intrinsic-list-translation.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch-too-many.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch-too-many.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch-too-many.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch-too-many.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch-too-many.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch-too-many.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch-too-many.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch-too-many.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/list-parameter-count-mismatch.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/list-parameter-count-mismatch.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params-struct.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params-struct.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params-struct.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params-struct.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params-struct.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params-struct.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-generic-params.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-generic-params.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-type-extraction.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-type-extraction.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-type-extraction.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-type-extraction.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-type-extraction.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-type-extraction.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/locals-type-extraction.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/locals-type-extraction.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-self.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-self.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-self.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-self.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-self.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-self.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-self.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-self.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-type.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-type.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/non-contractive-type.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/non-contractive-type.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-global-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-global-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-global-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-global-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-global-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-global-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-global-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-global-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-local-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-local-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-local-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-local-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-local-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-local-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/path-local-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/path-local-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/result.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/result.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/result.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/result.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/result.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/result.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/result.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/result.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-infer.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-infer.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-infer.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-infer.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-infer.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-infer.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-infer.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-infer.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-intersection.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-intersection.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-intersection.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-intersection.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-intersection.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-intersection.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-intersection.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-intersection.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-struct.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-struct.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-struct.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-struct.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-struct.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-struct.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-struct.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-struct.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-tuple.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-tuple.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-tuple.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-tuple.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-tuple.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-tuple.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-union.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-union.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-union.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-union.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-union.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-union.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/translation-union.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/translation-union.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/unused-generic-parameter.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/unused-generic-parameter.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/unused-generic-parameter.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/unused-generic-parameter.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/unused-generic-parameter.stderr b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/unused-generic-parameter.stderr similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/unused-generic-parameter.stderr rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/definition/unused-generic-parameter.stderr diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/generic-type-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/generic-type-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/generic-type-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/generic-type-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/generic-type-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/generic-type-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/generic-type-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/generic-type-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/global-type-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/global-type-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/global-type-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/global-type-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/global-type-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/global-type-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/global-type-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/global-type-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/graph-pipeline-inferred-closure.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/graph-pipeline-inferred-closure.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/graph-pipeline-inferred-closure.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/graph-pipeline-inferred-closure.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/graph-pipeline-inferred-closure.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/graph-pipeline-inferred-closure.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/graph-pipeline-inferred-closure.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/graph-pipeline-inferred-closure.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/let-binding-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/let-binding-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/let-binding-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/let-binding-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/let-binding-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/let-binding-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/let-binding-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/let-binding-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/local-type-resolution.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/local-type-resolution.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/local-type-resolution.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/local-type-resolution.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/local-type-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/local-type-resolution.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/local-type-resolution.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/local-type-resolution.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/nested-type-nodes.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/nested-type-nodes.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/nested-type-nodes.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/nested-type-nodes.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/nested-type-nodes.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/nested-type-nodes.stdout similarity index 98% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/nested-type-nodes.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/nested-type-nodes.stdout index 0fd15364ab1..fac2926bc88 100644 --- a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/nested-type-nodes.stdout +++ b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/nested-type-nodes.stdout @@ -28,9 +28,9 @@ Expr#0@47 LiteralExpr#18@35 Primitive (Integer) Integer (2) - Type#19@22 + Type#19@23 TypeKind (Struct) - StructType#20@22 + StructType#20@23 StructField#21@21 (name: data) Type#22@20 TypeKind (Path) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/union-type.jsonc b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/union-type.jsonc similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/union-type.jsonc rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/union-type.jsonc diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/union-type.stdout b/libs/@local/hashql/ast/tests/ui/lower/type-extractor/union-type.stdout similarity index 100% rename from libs/@local/hashql/ast/tests/ui/lowering/type-extractor/union-type.stdout rename to libs/@local/hashql/ast/tests/ui/lower/type-extractor/union-type.stdout diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/.spec.toml deleted file mode 100644 index 06841add3ea..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/import-resolver" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/.spec.toml deleted file mode 100644 index 32a91777a7a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/import-resolver/continue" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stderr deleted file mode 100644 index e4c88b40f45..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[import-resolver::unresolved-variable]: Unresolved variable - ╭▸ -5 │ "Foo", - │ ━━━ Cannot find variable 'Bar' - │ - ├ help: The name 'Bar' doesn't exist in this scope. - ╰ note: Variables must be defined before they can be used. This could be a typo, a variable used outside its scope, or a missing declaration. If it's a function or type from another module, you might need to import it first. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stdout b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stdout deleted file mode 100644 index 29720f36808..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-nested.stdout +++ /dev/null @@ -1,14 +0,0 @@ -Expr#4294967040@19 - ExprKind (Type) - TypeExpr#4294967040@19 (name: Foo) - GenericConstraint#4294967040@10 (name: T) - Type#4294967040@9 - TypeKind (Dummy) - Type#4294967040@16 - TypeKind (Path) - Path#4294967040@16 (rooted: true) - PathSegment#4294967040@15 (name: kernel) - PathSegment#4294967040@15 (name: type) - PathSegment#4294967040@15 (name: Number) - Expr#4294967040@18 - ExprKind (Underscore) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stderr deleted file mode 100644 index 25feff0dc70..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-type.stderr +++ /dev/null @@ -1,18 +0,0 @@ -error[import-resolver::unresolved-variable]: Unresolved variable - ╭▸ -6 │ "T", - │ ━ Cannot find variable 'T' - │ - ├ help: The name 'T' doesn't exist in this scope. - │ - │ Did you mean one of these local variables: - │ - Foo - │ - │ Or perhaps you meant one of these imported items: - │ - Result - │ - Some - │ - String - │ - Unknown - │ - Url - │ (13 more available) - ╰ note: Variables must be defined before they can be used. This could be a typo, a variable used outside its scope, or a missing declaration. If it's a function or type from another module, you might need to import it first. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stderr deleted file mode 100644 index 9cddf97bb09..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/unresolved-value.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[import-resolver::unresolved-variable]: Unresolved variable - ╭▸ -4 │ "shl", - │ ━━━ Cannot find variable 'shl' - │ - ├ help: The name 'shl' doesn't exist in this scope. - │ - │ Items with a similar name exist in other modules: - │ - ::core::bits::shl - │ - │ To use an item, you can either: - │ 1. Import it: use ::core::bits::shl in - │ 2. Use fully qualified path: ::core::bits::shl - ╰ note: Variables must be defined before they can be used. This could be a typo, a variable used outside its scope, or a missing declaration. If it's a function or type from another module, you might need to import it first. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.stderr deleted file mode 100644 index 16771f2c5c4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/continue/use-not-found.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[import-resolver::unresolved-import]: Unresolved import - ╭▸ -5 │ "kernel::foo", - │ ┬───────┯━━ - │ │ │ - │ │ Module 'foo' not found - │ In this path - │ - ├ help: The module 'kernel::foo' doesn't exist in this scope. Check the spelling and ensure the module is available. - ╰ note: Modules must be properly defined and exported from their parent module to be accessible. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.stderr deleted file mode 100644 index 86516eb6459..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-absolute-path.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[import-resolver::generic-arguments-in-module]: Generic arguments only allowed in final path segment - ╭▸ -3 │ ["::math::add", { "#literal": 2 }, { "#literal": 3 }] - │ ━ Remove this generic argument - │ - ├ help: Generic arguments can only appear on the final type in a path. Remove them from this module segment or move them to the final type in the path. - │ - │ Correct: `module::submodule::Type` - │ Incorrect: `module::submodule::Type` - ╰ note: Module paths don't accept generic parameters because modules themselves aren't generic. Only the final type in a path can have generic parameters. - - The path resolution happens before any generic type checking, so generic arguments can only be applied after the item is found. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.jsonc deleted file mode 100644 index e673c001af0..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -//@ run: fail -//@ description: Tests that generic arguments in 'use' statements are rejected with appropriate error -["use", "kernel::type", { "#tuple": ["T"] }, "_"] -//~^ ERROR Generic arguments are not allowed here diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.stderr deleted file mode 100644 index ec9fba73da4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/generic-argument-use-path.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::use-path-with-generics]: Use path with generic arguments - ╭▸ -3 │ ["use", "kernel::type", { "#tuple": ["T"] }, "_"] - │ ┯━━━━━━━┬────── - │ │ │ - │ │ Generic arguments are not allowed here - │ Remove these generic arguments - │ - ├ help: The 'use' special form does not support generic arguments in import paths. Remove all generic arguments from the path. - ╰ note: Use statements in HashQL can only import modules or specific symbols, but cannot specify generic parameters during import. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.stderr deleted file mode 100644 index 00a417bae80..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/glob-not-found.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[import-resolver::unresolved-import]: Unresolved import - ╭▸ -3 │ ["use", "kernel::foo", "*", "_"] - │ ┬───────┯━━ - │ │ │ - │ │ Module 'foo' not found - │ In this path - │ - ├ help: The module 'kernel::foo' doesn't exist in this scope. Check the spelling and ensure the module is available. - ╰ note: Modules must be properly defined and exported from their parent module to be accessible. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.stderr deleted file mode 100644 index 447389a421d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/item-not-found.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[import-resolver::unresolved-import]: Unresolved import - ╭▸ -3 │ ["use", "core::bits", { "#tuple": ["lshift"] }, "_"] - │ ──── This module ━━━━━━ 'lshift' not found in module 'core::bits' - │ - ├ help: Check the spelling and ensure the item is exported and available in this context. - ╰ note: Items must be defined and accessible from the importing location. Make sure the item exists and is public. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.stderr deleted file mode 100644 index 175adbbbc8e..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/module-not-found.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[import-resolver::unresolved-import]: Unresolved import - ╭▸ -3 │ ["use", "kernel::foo", { "#tuple": ["baz"] }, "_"] - │ ┬───────┯━━ - │ │ │ - │ │ Module 'foo' not found - │ In this path - │ - ├ help: The module 'kernel::foo' doesn't exist in this scope. Check the spelling and ensure the module is available. - ╰ note: Modules must be properly defined and exported from their parent module to be accessible. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.stderr deleted file mode 100644 index dd377a57377..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/rollback.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[import-resolver::unresolved-variable]: Unresolved variable - ╭▸ -7 │ "shl" - │ ━━━ Cannot find variable 'shl' - │ - ├ help: The name 'shl' doesn't exist in this scope. - │ - │ Items with a similar name exist in other modules: - │ - ::core::bits::shl - │ - │ To use an item, you can either: - │ 1. Import it: use ::core::bits::shl in - │ 2. Use fully qualified path: ::core::bits::shl - ╰ note: Variables must be defined before they can be used. This could be a typo, a variable used outside its scope, or a missing declaration. If it's a function or type from another module, you might need to import it first. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.stderr b/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.stderr deleted file mode 100644 index 4ae61191046..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/import-resolver/unresolver-variable.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[import-resolver::unresolved-variable]: Unresolved variable - ╭▸ -11 │ "föo" - │ ━━━ Cannot find variable 'föo' - │ - ├ help: The name 'föo' doesn't exist in this scope. - │ - │ Did you mean one of these local variables: - │ - foo - ╰ note: Variables must be defined before they can be used. This could be a typo, a variable used outside its scope, or a missing declaration. If it's a function or type from another module, you might need to import it first. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/.spec.toml deleted file mode 100644 index 9c6ce418bf1..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/name-mangler" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.jsonc deleted file mode 100644 index 7566ff7708d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: Absolute paths should stay unchanged. -["let", "a", { "#literal": 2 }, "::a"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.stdout deleted file mode 100644 index 73484e71eed..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/absolute-path.stdout +++ /dev/null @@ -1,12 +0,0 @@ -Expr#4294967040@14 - ExprKind (Let) - LetExpr#4294967040@14 (name: a:0) - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (Integer) - Integer (2) - Expr#4294967040@13 - ExprKind (Path) - Path#4294967040@13 (rooted: true) - PathSegment#4294967040@12 (name: a) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.jsonc deleted file mode 100644 index dd78d16c77c..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: test that arguments are mangled correctly -["fn", { "#tuple": [] }, { "#struct": { "a": "T" } }, "_", ["as", "a", "T"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.stdout deleted file mode 100644 index 9d68dc11204..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-args.stdout +++ /dev/null @@ -1,23 +0,0 @@ -Expr#4294967040@30 - ExprKind (Closure) - ClosureExpr#4294967040@30 - ClosureSignature#4294967040@30 - Generics#4294967040@4 - ClosureParam#4294967040@12 (name: a:0) - Type#4294967040@11 - TypeKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: T) - Type#4294967040@16 - TypeKind (Infer) - Expr#4294967040@29 - ExprKind (As) - AsExpr#4294967040@29 - Expr#4294967040@24 - ExprKind (Path) - Path#4294967040@24 (rooted: false) - PathSegment#4294967040@23 (name: a:0) - Type#4294967040@28 - TypeKind (Path) - Path#4294967040@28 (rooted: false) - PathSegment#4294967040@27 (name: T) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.stdout deleted file mode 100644 index 66b7c688dd3..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/fn-types.stdout +++ /dev/null @@ -1,27 +0,0 @@ -Expr#4294967040@40 - ExprKind (Closure) - ClosureExpr#4294967040@40 - ClosureSignature#4294967040@40 - Generics#4294967040@12 - GenericParam#4294967040@7 (name: T:0) - GenericParam#4294967040@11 (name: U:0) - Type#4294967040@17 - TypeKind (Infer) - Expr#4294967040@39 - ExprKind (As) - AsExpr#4294967040@39 - Expr#4294967040@34 - ExprKind (As) - AsExpr#4294967040@34 - Expr#4294967040@29 - ExprKind (Path) - Path#4294967040@29 (rooted: false) - PathSegment#4294967040@28 (name: T) - Type#4294967040@33 - TypeKind (Path) - Path#4294967040@33 (rooted: false) - PathSegment#4294967040@32 (name: T:0) - Type#4294967040@38 - TypeKind (Path) - Path#4294967040@38 (rooted: false) - PathSegment#4294967040@37 (name: U:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.jsonc deleted file mode 100644 index 0d423afd218..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: mangle the name of a simple type expression -["newtype", "T", "X", ["as", "T", "T"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.stdout deleted file mode 100644 index 72966aec918..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/newtype.stdout +++ /dev/null @@ -1,18 +0,0 @@ -Expr#4294967040@25 - ExprKind (NewType) - NewTypeExpr#4294967040@25 (name: T:0) - Type#4294967040@11 - TypeKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: X) - Expr#4294967040@24 - ExprKind (As) - AsExpr#4294967040@24 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: T:0) - Type#4294967040@23 - TypeKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.jsonc deleted file mode 100644 index bef8888fa1f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.jsonc +++ /dev/null @@ -1,12 +0,0 @@ -//@ run: pass -//@ description: Make sure that if we have an "onion" of bindings, the outer binding is preserved on exit -[ - "type", - "T", - "U", - [ - "*", - ["fn", { "#tuple": ["T", "U"] }, { "#struct": {} }, "_", ["as", "a", "T"]], - ["as", "a", "T"] - ] -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.stdout deleted file mode 100644 index ea1ce33f869..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/onion.stdout +++ /dev/null @@ -1,49 +0,0 @@ -Expr#4294967040@62 - ExprKind (Type) - TypeExpr#4294967040@62 (name: T:0) - Type#4294967040@11 - TypeKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: U) - Expr#4294967040@61 - ExprKind (Call) - CallExpr#4294967040@61 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: core) - PathSegment#4294967040@14 (name: math) - PathSegment#4294967040@14 (name: mul) - Argument#4294967040@47 - Expr#4294967040@47 - ExprKind (Closure) - ClosureExpr#4294967040@47 - ClosureSignature#4294967040@47 - Generics#4294967040@28 - GenericParam#4294967040@23 (name: T:1) - GenericParam#4294967040@27 (name: U:0) - Type#4294967040@33 - TypeKind (Infer) - Expr#4294967040@46 - ExprKind (As) - AsExpr#4294967040@46 - Expr#4294967040@41 - ExprKind (Path) - Path#4294967040@41 (rooted: false) - PathSegment#4294967040@40 (name: a) - Type#4294967040@45 - TypeKind (Path) - Path#4294967040@45 (rooted: false) - PathSegment#4294967040@44 (name: T:1) - Argument#4294967040@60 - Expr#4294967040@60 - ExprKind (As) - AsExpr#4294967040@60 - Expr#4294967040@55 - ExprKind (Path) - Path#4294967040@55 (rooted: false) - PathSegment#4294967040@54 (name: a) - Type#4294967040@59 - TypeKind (Path) - Path#4294967040@59 (rooted: false) - PathSegment#4294967040@58 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.stdout deleted file mode 100644 index 9b063d178cd..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/path.stdout +++ /dev/null @@ -1,13 +0,0 @@ -Expr#4294967040@16 - ExprKind (Let) - LetExpr#4294967040@16 (name: a:0) - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (Integer) - Integer (2) - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@12 (name: a:0) - PathSegment#4294967040@14 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.stdout b/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.stdout deleted file mode 100644 index ada74400e1d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/name-mangler/type.stdout +++ /dev/null @@ -1,18 +0,0 @@ -Expr#4294967040@25 - ExprKind (Type) - TypeExpr#4294967040@25 (name: T:0) - Type#4294967040@11 - TypeKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: X) - Expr#4294967040@24 - ExprKind (As) - AsExpr#4294967040@24 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: T) - Type#4294967040@23 - TypeKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: T:0) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/.spec.toml deleted file mode 100644 index b2dac700d22..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/node-renumberer/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/node-renumberer" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/.spec.toml deleted file mode 100644 index 51611c0cbf3..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/pre-expansion-name-resolver" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-absolute-path.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-absolute-path.stdout deleted file mode 100644 index b7e15b93a7f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-absolute-path.stdout +++ /dev/null @@ -1,43 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@10 (name: core) - PathSegment#4294967040@12 (name: math) - PathSegment#4294967040@14 (name: add) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@18 (name: core) - PathSegment#4294967040@18 (name: math) - PathSegment#4294967040@18 (name: add) - Argument#4294967040@21 - Expr#4294967040@21 - ExprKind (Literal) - LiteralExpr#4294967040@20 - Primitive (Integer) - Integer (1) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Literal) - LiteralExpr#4294967040@22 - Primitive (Integer) - Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.stdout deleted file mode 100644 index 2ffabb4fe02..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/alias-symbol.stdout +++ /dev/null @@ -1,41 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: !&&) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: core) - PathSegment#4294967040@10 (name: bool) - PathSegment#4294967040@10 (name: and) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: core) - PathSegment#4294967040@14 (name: bool) - PathSegment#4294967040@14 (name: and) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: a) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.jsonc deleted file mode 100644 index 19bfcb831df..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: Test let expression with non-path target is not treated specially -["let", ["+", "a", "b"], "c", "body"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.stdout deleted file mode 100644 index b733d7ff4d9..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/invalid-let-expr.stdout +++ /dev/null @@ -1,39 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@16 - Expr#4294967040@16 - ExprKind (Call) - CallExpr#4294967040@16 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: true) - PathSegment#4294967040@6 (name: core) - PathSegment#4294967040@6 (name: math) - PathSegment#4294967040@6 (name: add) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: a) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@20 - Expr#4294967040@20 - ExprKind (Path) - Path#4294967040@20 (rooted: false) - PathSegment#4294967040@19 (name: c) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Path) - Path#4294967040@24 (rooted: false) - PathSegment#4294967040@23 (name: body) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.stdout deleted file mode 100644 index 141768f4636..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3-alias.stdout +++ /dev/null @@ -1,37 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: x) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.stdout deleted file mode 100644 index 28e7e58fe3d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-3.stdout +++ /dev/null @@ -1,41 +0,0 @@ -Expr#4294967040@21 - ExprKind (Call) - CallExpr#4294967040@21 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@20 - Expr#4294967040@20 - ExprKind (Call) - CallExpr#4294967040@20 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: core) - PathSegment#4294967040@14 (name: math) - PathSegment#4294967040@14 (name: add) - Argument#4294967040@17 - Expr#4294967040@17 - ExprKind (Literal) - LiteralExpr#4294967040@16 - Primitive (Integer) - Integer (2) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Literal) - LiteralExpr#4294967040@18 - Primitive (Integer) - Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.stdout deleted file mode 100644 index ebbc73cce13..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4-alias.stdout +++ /dev/null @@ -1,42 +0,0 @@ -Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: Int) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Call) - CallExpr#4294967040@28 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: b) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) - Argument#4294967040@27 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: false) - PathSegment#4294967040@26 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.jsonc deleted file mode 100644 index c8e03a72e71..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: Test that `let/4` is resolved to `::kernel::special_form::let` -["let", "a", "Int", "b", ["+", { "#literal": 2 }, { "#literal": 3 }]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.stdout deleted file mode 100644 index a711ce7f1c5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-4.stdout +++ /dev/null @@ -1,46 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: Int) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@18 (name: core) - PathSegment#4294967040@18 (name: math) - PathSegment#4294967040@18 (name: add) - Argument#4294967040@21 - Expr#4294967040@21 - ExprKind (Literal) - LiteralExpr#4294967040@20 - Primitive (Integer) - Integer (2) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Literal) - LiteralExpr#4294967040@22 - Primitive (Integer) - Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.stdout deleted file mode 100644 index fb06af2297e..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-absolute.stdout +++ /dev/null @@ -1,37 +0,0 @@ -Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@4 (name: special_form) - PathSegment#4294967040@6 (name: let) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: a) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Call) - CallExpr#4294967040@28 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: b) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) - Argument#4294967040@27 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: false) - PathSegment#4294967040@26 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.jsonc deleted file mode 100644 index 10ec4818b0a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: if the first argument cannot be interpreted as an ident, do nothing -["let", "a", "b", ["+", "a", "c"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.stdout deleted file mode 100644 index 03e08b2113a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/let-ident-generic.stdout +++ /dev/null @@ -1,45 +0,0 @@ -Expr#4294967040@30 - ExprKind (Call) - CallExpr#4294967040@30 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@12 - Expr#4294967040@12 - ExprKind (Path) - Path#4294967040@12 (rooted: false) - PathSegment#4294967040@11 (name: a) - PathSegmentArgument (GenericArgument) - GenericArgument#4294967040@10 - Type#4294967040@9 - TypeKind (Path) - Path#4294967040@9 (rooted: false) - PathSegment#4294967040@8 (name: T) - Argument#4294967040@16 - Expr#4294967040@16 - ExprKind (Path) - Path#4294967040@16 (rooted: false) - PathSegment#4294967040@15 (name: b) - Argument#4294967040@29 - Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@20 - ExprKind (Path) - Path#4294967040@20 (rooted: true) - PathSegment#4294967040@19 (name: core) - PathSegment#4294967040@19 (name: math) - PathSegment#4294967040@19 (name: add) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Path) - Path#4294967040@24 (rooted: false) - PathSegment#4294967040@23 (name: a) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Path) - Path#4294967040@28 (rooted: false) - PathSegment#4294967040@27 (name: c) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.stdout deleted file mode 100644 index 6faf0ff783b..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/multi-segment-resolution.stdout +++ /dev/null @@ -1,10 +0,0 @@ -Expr#4294967040@6 - ExprKind (Call) - CallExpr#4294967040@6 - Expr#4294967040@5 - ExprKind (Path) - Path#4294967040@5 (rooted: true) - PathSegment#4294967040@2 (name: core) - PathSegment#4294967040@2 (name: math) - PathSegment#4294967040@2 (name: add) - PathSegment#4294967040@4 (name: total) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.stdout deleted file mode 100644 index 4b40c0f6a26..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/nested-let.stdout +++ /dev/null @@ -1,81 +0,0 @@ -Expr#4294967040@50 - ExprKind (Call) - CallExpr#4294967040@50 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: c) - Argument#4294967040@49 - Expr#4294967040@49 - ExprKind (Call) - CallExpr#4294967040@49 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: kernel) - PathSegment#4294967040@14 (name: special_form) - PathSegment#4294967040@14 (name: let) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: b) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: d) - Argument#4294967040@48 - Expr#4294967040@48 - ExprKind (Call) - CallExpr#4294967040@48 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: true) - PathSegment#4294967040@26 (name: kernel) - PathSegment#4294967040@26 (name: special_form) - PathSegment#4294967040@26 (name: let) - Argument#4294967040@31 - Expr#4294967040@31 - ExprKind (Path) - Path#4294967040@31 (rooted: false) - PathSegment#4294967040@30 (name: a) - Argument#4294967040@35 - Expr#4294967040@35 - ExprKind (Path) - Path#4294967040@35 (rooted: false) - PathSegment#4294967040@34 (name: e) - Argument#4294967040@47 - Expr#4294967040@47 - ExprKind (Call) - CallExpr#4294967040@47 - Expr#4294967040@39 - ExprKind (Path) - Path#4294967040@39 (rooted: false) - PathSegment#4294967040@38 (name: e) - Argument#4294967040@46 - Expr#4294967040@46 - ExprKind (Call) - CallExpr#4294967040@46 - Expr#4294967040@43 - ExprKind (Path) - Path#4294967040@43 (rooted: false) - PathSegment#4294967040@42 (name: d) - Argument#4294967040@45 - Expr#4294967040@45 - ExprKind (Literal) - LiteralExpr#4294967040@44 - Primitive (Integer) - Integer (2) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.jsonc deleted file mode 100644 index 94876c29029..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: if given that `a = b`, then `a` should be replaced with `b` in the body -["newtype", "a", "b", ["a", "x", "x"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.stdout deleted file mode 100644 index 9e073091e15..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3-alias.stdout +++ /dev/null @@ -1,37 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: newtype) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: a) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: x) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.stdout deleted file mode 100644 index b25c6b7f7cc..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-3.stdout +++ /dev/null @@ -1,41 +0,0 @@ -Expr#4294967040@21 - ExprKind (Call) - CallExpr#4294967040@21 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: newtype) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@20 - Expr#4294967040@20 - ExprKind (Call) - CallExpr#4294967040@20 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: core) - PathSegment#4294967040@14 (name: math) - PathSegment#4294967040@14 (name: add) - Argument#4294967040@17 - Expr#4294967040@17 - ExprKind (Literal) - LiteralExpr#4294967040@16 - Primitive (Integer) - Integer (2) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Literal) - LiteralExpr#4294967040@18 - Primitive (Integer) - Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.jsonc deleted file mode 100644 index 2d63d131230..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: Test that `newtype/4` is resolved to `::kernel::special_form::newtype`, but that no aliases are created. -["newtype", "a", "Int", "b", ["+", "a", "c"]] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.stdout deleted file mode 100644 index 98d7f99057a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/newtype-4.stdout +++ /dev/null @@ -1,44 +0,0 @@ -Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: newtype) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: Int) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Call) - CallExpr#4294967040@28 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@18 (name: core) - PathSegment#4294967040@18 (name: math) - PathSegment#4294967040@18 (name: add) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: a) - Argument#4294967040@27 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: false) - PathSegment#4294967040@26 (name: c) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout deleted file mode 100644 index 49681abda92..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude-diverging.stdout +++ /dev/null @@ -1,89 +0,0 @@ -Expr#4294967040@50 - ExprKind (Call) - CallExpr#4294967040@50 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: if) - Argument#4294967040@5 - Expr#4294967040@5 - ExprKind (Literal) - LiteralExpr#4294967040@4 - Primitive (True) - Argument#4294967040@36 - Expr#4294967040@36 - ExprKind (Call) - CallExpr#4294967040@36 - Expr#4294967040@9 - ExprKind (Path) - Path#4294967040@9 (rooted: true) - PathSegment#4294967040@8 (name: kernel) - PathSegment#4294967040@8 (name: special_form) - PathSegment#4294967040@8 (name: let) - Argument#4294967040@13 - Expr#4294967040@13 - ExprKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: let) - Argument#4294967040@22 - Expr#4294967040@22 - ExprKind (Call) - CallExpr#4294967040@22 - Expr#4294967040@17 - ExprKind (Path) - Path#4294967040@17 (rooted: true) - PathSegment#4294967040@16 (name: core) - PathSegment#4294967040@16 (name: math) - PathSegment#4294967040@16 (name: add) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Literal) - LiteralExpr#4294967040@18 - Primitive (Integer) - Integer (1) - Argument#4294967040@21 - Expr#4294967040@21 - ExprKind (Literal) - LiteralExpr#4294967040@20 - Primitive (Integer) - Integer (2) - Argument#4294967040@35 - Expr#4294967040@35 - ExprKind (Call) - CallExpr#4294967040@35 - Expr#4294967040@26 - ExprKind (Path) - Path#4294967040@26 (rooted: false) - PathSegment#4294967040@25 (name: let) - Argument#4294967040@30 - Expr#4294967040@30 - ExprKind (Path) - Path#4294967040@30 (rooted: false) - PathSegment#4294967040@29 (name: a) - Argument#4294967040@34 - Expr#4294967040@34 - ExprKind (Path) - Path#4294967040@34 (rooted: false) - PathSegment#4294967040@33 (name: b) - Argument#4294967040@49 - Expr#4294967040@49 - ExprKind (Call) - CallExpr#4294967040@49 - Expr#4294967040@40 - ExprKind (Path) - Path#4294967040@40 (rooted: true) - PathSegment#4294967040@39 (name: kernel) - PathSegment#4294967040@39 (name: special_form) - PathSegment#4294967040@39 (name: let) - Argument#4294967040@44 - Expr#4294967040@44 - ExprKind (Path) - Path#4294967040@44 (rooted: false) - PathSegment#4294967040@43 (name: a) - Argument#4294967040@48 - Expr#4294967040@48 - ExprKind (Path) - Path#4294967040@48 (rooted: false) - PathSegment#4294967040@47 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.stdout deleted file mode 100644 index ba76ffc67ae..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign-prelude.stdout +++ /dev/null @@ -1,54 +0,0 @@ -Expr#4294967040@30 - ExprKind (Call) - CallExpr#4294967040@30 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: let) - Argument#4294967040@16 - Expr#4294967040@16 - ExprKind (Call) - CallExpr#4294967040@16 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: core) - PathSegment#4294967040@10 (name: math) - PathSegment#4294967040@10 (name: add) - Argument#4294967040@13 - Expr#4294967040@13 - ExprKind (Literal) - LiteralExpr#4294967040@12 - Primitive (Integer) - Integer (1) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Literal) - LiteralExpr#4294967040@14 - Primitive (Integer) - Integer (2) - Argument#4294967040@29 - Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@20 - ExprKind (Path) - Path#4294967040@20 (rooted: false) - PathSegment#4294967040@19 (name: let) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Path) - Path#4294967040@24 (rooted: false) - PathSegment#4294967040@23 (name: a) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Path) - Path#4294967040@28 (rooted: false) - PathSegment#4294967040@27 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.stdout deleted file mode 100644 index c985d638d83..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/re-assign.stdout +++ /dev/null @@ -1,79 +0,0 @@ -Expr#4294967040@47 - ExprKind (Call) - CallExpr#4294967040@47 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: let) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: c) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: d) - Argument#4294967040@46 - Expr#4294967040@46 - ExprKind (Call) - CallExpr#4294967040@46 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: kernel) - PathSegment#4294967040@14 (name: special_form) - PathSegment#4294967040@14 (name: let) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: b) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: d) - Argument#4294967040@45 - Expr#4294967040@45 - ExprKind (Call) - CallExpr#4294967040@45 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: true) - PathSegment#4294967040@26 (name: kernel) - PathSegment#4294967040@26 (name: special_form) - PathSegment#4294967040@26 (name: let) - Argument#4294967040@31 - Expr#4294967040@31 - ExprKind (Path) - Path#4294967040@31 (rooted: false) - PathSegment#4294967040@30 (name: a) - Argument#4294967040@35 - Expr#4294967040@35 - ExprKind (Path) - Path#4294967040@35 (rooted: false) - PathSegment#4294967040@34 (name: d) - Argument#4294967040@44 - Expr#4294967040@44 - ExprKind (Call) - CallExpr#4294967040@44 - Expr#4294967040@39 - ExprKind (Path) - Path#4294967040@39 (rooted: false) - PathSegment#4294967040@38 (name: d) - Argument#4294967040@41 - Expr#4294967040@41 - ExprKind (Literal) - LiteralExpr#4294967040@40 - Primitive (Integer) - Integer (1) - Argument#4294967040@43 - Expr#4294967040@43 - ExprKind (Literal) - LiteralExpr#4294967040@42 - Primitive (Integer) - Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.stdout deleted file mode 100644 index be4e5ded3ef..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/resolve-symbol.stdout +++ /dev/null @@ -1,19 +0,0 @@ -Expr#4294967040@12 - ExprKind (Call) - CallExpr#4294967040@12 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: core) - PathSegment#4294967040@2 (name: bool) - PathSegment#4294967040@2 (name: and) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.jsonc deleted file mode 100644 index 0a7c5d13832..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.jsonc +++ /dev/null @@ -1,9 +0,0 @@ -//@ run: pass -//@ description: Test bindings are restored after let expressions -// prettier-ignore -["if", {"#literal": true}, - ["let", "x", "outer", - ["x", {"#literal": 2}, { "#literal": 1 }] - ], - ["x", { "#literal": 2 }, { "#literal": 1 }] -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.stdout deleted file mode 100644 index 74a7e688cb6..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/restoration-bindings.stdout +++ /dev/null @@ -1,74 +0,0 @@ -Expr#4294967040@37 - ExprKind (Call) - CallExpr#4294967040@37 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: if) - Argument#4294967040@5 - Expr#4294967040@5 - ExprKind (Literal) - LiteralExpr#4294967040@4 - Primitive (True) - Argument#4294967040@27 - Expr#4294967040@27 - ExprKind (Call) - CallExpr#4294967040@27 - Expr#4294967040@9 - ExprKind (Path) - Path#4294967040@9 (rooted: true) - PathSegment#4294967040@8 (name: kernel) - PathSegment#4294967040@8 (name: special_form) - PathSegment#4294967040@8 (name: let) - Argument#4294967040@13 - Expr#4294967040@13 - ExprKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: x) - Argument#4294967040@17 - Expr#4294967040@17 - ExprKind (Path) - Path#4294967040@17 (rooted: false) - PathSegment#4294967040@16 (name: outer) - Argument#4294967040@26 - Expr#4294967040@26 - ExprKind (Call) - CallExpr#4294967040@26 - Expr#4294967040@21 - ExprKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: outer) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Literal) - LiteralExpr#4294967040@22 - Primitive (Integer) - Integer (2) - Argument#4294967040@25 - Expr#4294967040@25 - ExprKind (Literal) - LiteralExpr#4294967040@24 - Primitive (Integer) - Integer (1) - Argument#4294967040@36 - Expr#4294967040@36 - ExprKind (Call) - CallExpr#4294967040@36 - Expr#4294967040@31 - ExprKind (Path) - Path#4294967040@31 (rooted: false) - PathSegment#4294967040@30 (name: x) - Argument#4294967040@33 - Expr#4294967040@33 - ExprKind (Literal) - LiteralExpr#4294967040@32 - Primitive (Integer) - Integer (2) - Argument#4294967040@35 - Expr#4294967040@35 - ExprKind (Literal) - LiteralExpr#4294967040@34 - Primitive (Integer) - Integer (1) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.stdout deleted file mode 100644 index 338df7a6901..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3-alias.stdout +++ /dev/null @@ -1,37 +0,0 @@ -Expr#4294967040@25 - ExprKind (Call) - CallExpr#4294967040@25 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: type) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@24 - Expr#4294967040@24 - ExprKind (Call) - CallExpr#4294967040@24 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: a) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: x) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.stdout deleted file mode 100644 index aca18297238..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-3.stdout +++ /dev/null @@ -1,41 +0,0 @@ -Expr#4294967040@21 - ExprKind (Call) - CallExpr#4294967040@21 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: type) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: b) - Argument#4294967040@20 - Expr#4294967040@20 - ExprKind (Call) - CallExpr#4294967040@20 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: true) - PathSegment#4294967040@14 (name: core) - PathSegment#4294967040@14 (name: math) - PathSegment#4294967040@14 (name: add) - Argument#4294967040@17 - Expr#4294967040@17 - ExprKind (Literal) - LiteralExpr#4294967040@16 - Primitive (Integer) - Integer (2) - Argument#4294967040@19 - Expr#4294967040@19 - ExprKind (Literal) - LiteralExpr#4294967040@18 - Primitive (Integer) - Integer (3) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.stdout b/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.stdout deleted file mode 100644 index 48a646afb31..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/pre-expansion-name-resolver/type-4.stdout +++ /dev/null @@ -1,44 +0,0 @@ -Expr#4294967040@29 - ExprKind (Call) - CallExpr#4294967040@29 - Expr#4294967040@3 - ExprKind (Path) - Path#4294967040@3 (rooted: true) - PathSegment#4294967040@2 (name: kernel) - PathSegment#4294967040@2 (name: special_form) - PathSegment#4294967040@2 (name: type) - Argument#4294967040@7 - Expr#4294967040@7 - ExprKind (Path) - Path#4294967040@7 (rooted: false) - PathSegment#4294967040@6 (name: a) - Argument#4294967040@11 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: Int) - Argument#4294967040@15 - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: b) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Call) - CallExpr#4294967040@28 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@18 (name: core) - PathSegment#4294967040@18 (name: math) - PathSegment#4294967040@18 (name: add) - Argument#4294967040@23 - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: a) - Argument#4294967040@27 - Expr#4294967040@27 - ExprKind (Path) - Path#4294967040@27 (rooted: false) - PathSegment#4294967040@26 (name: c) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/.spec.toml deleted file mode 100644 index 3d770678cbf..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/sanitizer" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.stderr b/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.stderr deleted file mode 100644 index f16bad42ef5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/sanitizer/special-form-type-position.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[sanitizer::invalid-special-form]: Invalid special form - ╭▸ -6 │ "::kernel::special_form::let", - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Special form cannot be used as a type - │ - ├ help: The special form 'let' must be called directly with arguments. It cannot be used as a type annotation or declaration or passed to other functions. Instead, use it in a direct function call syntax. - ╰ note: Special forms in HashQL are compile-time constructs that must be expanded during compilation. They can only be used in call position with their expected arguments, not as regular values, types, or function references. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/.spec.toml deleted file mode 100644 index 15cabf9d93a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/special-form-expander" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.stderr deleted file mode 100644 index 95e4afe65bd..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-1.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::access", "x"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The access/2 form should look like: (. object field) - ╰ note: The access function has 1 variant: access/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.jsonc deleted file mode 100644 index d9b6a00257d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: access/2 should compile -["::kernel::special_form::access", "x", "y"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.stdout deleted file mode 100644 index bf7fa2f6400..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-2.stdout +++ /dev/null @@ -1,7 +0,0 @@ -Expr#4294967040@16 - ExprKind (Field) - FieldExpr#4294967040@16 (field: y) - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.stderr deleted file mode 100644 index 97af653e0ad..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-3.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ┌ [ -4 │ │ "::kernel::special_form::access", -5 │ │ "x", -6 │ │ "y", -7 │ │ "z" - │ │ ━ Remove this argument -8 │ │ //~^ ERROR Remove this argument -9 │ │ ] - │ └─┘ In this `access` special form call - │ - ├ help: The access/2 form should look like: (. object field) - ╰ note: The access function has 1 variant: access/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.stderr deleted file mode 100644 index 4c3afe10ee4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-index-out-of-bounds.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::field-index-out-of-bounds]: Field index out of bounds - ╭▸ -3 │ ["::kernel::special_form::access", "data", { "#literal": 18446744073709551616 }] - │ ━━━━━━━━━━━━━━━━━━━━ Use a valid field index within usize bounds - │ - ├ help: Field indices must be non-negative integers that fit within the bounds of usize. Very large numbers or negative numbers cannot be used as field indices. - ╰ note: Field indexing in HashQL uses zero-based indexing where the first field is at index 0, the second at index 1, and so on. The maximum valid index depends on the system's architecture, which is 64-bit. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.stderr deleted file mode 100644 index 3ec8e549e4a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-boolean.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-field-literal-type]: Invalid field literal type - ╭▸ -3 │ ["::kernel::special_form::access", "data", { "#literal": true }] - │ ━━━━ Use an integer literal here - │ - ├ help: Field access using literals requires an integer literal that specifies the field index. Other literal types like strings, booleans, or numbers cannot be used for field indexing. - ╰ note: Valid field access examples: - - (access tuple 0) - accesses first field - - (access record 2) - accesses third field - - (access data my_field) - accesses named field \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.stderr deleted file mode 100644 index 317a6962225..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-string.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-field-literal-type]: Invalid field literal type - ╭▸ -3 │ ["::kernel::special_form::access", "data", { "#literal": "field_name" }] - │ ━━━━━━━━━━━━ Use an integer literal here - │ - ├ help: Field access using literals requires an integer literal that specifies the field index. Other literal types like strings, booleans, or numbers cannot be used for field indexing. - ╰ note: Valid field access examples: - - (access tuple 0) - accesses first field - - (access record 2) - accesses third field - - (access data my_field) - accesses named field \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.jsonc deleted file mode 100644 index bab9003bf53..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -//@ run: fail -//@ description: Field literal with type annotation should error -[ - "::kernel::special_form::access", - "tuple", - { "#literal": 0, "#type": "Integer" } - //~^ ERROR Remove this type annotation -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.stderr deleted file mode 100644 index 699857743f4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-type-annotation.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::field-literal-type-annotation]: Field literal with type annotation - ╭▸ -6 │ { "#literal": 0, "#type": "Integer" } - │ ━━━━━━━ Remove this type annotation - │ - ├ help: Field access using numeric literals cannot have type annotations. The literal represents the field index directly and doesn't need a separate type. - ╰ note: When using numeric literals for field access (e.g., in tuple destructuring), the number itself indicates the position and cannot be annotated with a type. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.jsonc deleted file mode 100644 index eecae80a7b4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: Field access with valid integer literal should compile -["::kernel::special_form::access", "tuple", { "#literal": 0 }] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.stdout deleted file mode 100644 index a0039b09389..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/access-field-literal-valid.stdout +++ /dev/null @@ -1,7 +0,0 @@ -Expr#4294967040@14 - ExprKind (Field) - FieldExpr#4294967040@14 (field: 0) - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: tuple) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.stderr deleted file mode 100644 index 641b9786510..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-1.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::as", { "#literal": true }] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The as/2 form should look like: (as value type-expr) - ╰ note: The as function has 1 variant: as/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.stderr deleted file mode 100644 index 59559afebfd..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/as-3.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ┌ [ -4 │ │ "::kernel::special_form::as", -5 │ │ { "#literal": true }, -6 │ │ { "#literal": true }, -7 │ │ { "#literal": true } - │ │ ━━━━━━━━━━━━━━━━━━━━ Remove this argument -8 │ │ //~^ ERROR Remove this argument -9 │ │ ] - │ └─┘ In this `as` special form call - │ - ├ help: The as/2 form should look like: (as value type-expr) - ╰ note: The as function has 1 variant: as/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.stderr deleted file mode 100644 index 44f5875824f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-3.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::fn", { "#tuple": [] }, { "#struct": {} }, "_"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The fn/4 form should look like: (fn generics arguments return-type body) - ╰ note: The fn function has 1 variant: fn/4 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.stdout deleted file mode 100644 index 488a9c26ac9..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-4.stdout +++ /dev/null @@ -1,24 +0,0 @@ -Expr#4294967040@27 - ExprKind (Closure) - ClosureExpr#4294967040@27 - ClosureSignature#4294967040@27 - Generics#4294967040@8 - Type#4294967040@13 - TypeKind (Infer) - Expr#4294967040@26 - ExprKind (Call) - CallExpr#4294967040@26 - Expr#4294967040@17 - ExprKind (Path) - Path#4294967040@17 (rooted: false) - PathSegment#4294967040@16 (name: +) - Argument#4294967040@21 - Expr#4294967040@21 - ExprKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: a) - Argument#4294967040@25 - Expr#4294967040@25 - ExprKind (Path) - Path#4294967040@25 (rooted: false) - PathSegment#4294967040@24 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.stderr deleted file mode 100644 index 3eb8ecf37e5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-5.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::fn", - 5 │ │ { "#tuple": [] }, - 6 │ │ { "#struct": {} }, - ‡ │ - 9 │ │ ["*", "c", "d"] - │ │ ━━━━━━━━━━━━━━━ Remove this argument -10 │ │ //~^ ERROR Remove this argument -11 │ │ ] - │ └─┘ In this `fn` special form call - │ - ├ help: The fn/4 form should look like: (fn generics arguments return-type body) - ╰ note: The fn function has 1 variant: fn/4 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.stderr deleted file mode 100644 index f26820dcee8..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-duplicates.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::duplicate-generic-constraint]: Duplicate generic parameter constraint - ╭▸ -5 │ { "#tuple": ["T", "T"] }, - │ ┬ ━ Remove this duplicate generic parameter 'T' - │ │ - │ 'T' was previously declared here - │ - ├ help: Each generic parameter can only be declared once in a function definition. Remove the duplicate declaration or use a different name. - ╰ note: Generic parameter names must be unique within a function's generic parameter list. For example, in fn(param: T): U -> body), 'T' and 'U' are unique parameters. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.stderr deleted file mode 100644 index 75551a4d78c..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-literal.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-fn-generics-expression]: Invalid expression in function generics - ╭▸ -5 │ { "#literal": 2 }, - │ ━━━━━━━━━━━━━━━━━ Use a valid generics expression - │ - ├ help: Function generics must be specified as either a tuple of identifiers or a struct of bounded type parameters. Other expression types are not valid in this context. - ╰ note: Valid generics expressions include: - - Empty: () - - Tuple of identifiers: (T, U, V) - - Struct with bounds: (T: SomeBound, U: OtherBound) or (T: _, U: _) for unbounded types \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.stderr deleted file mode 100644 index 7742694bbef..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-literal.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -7 │ "T": { "#literal": 2 } - │ ━━━━━━━━━━━━━━━━━ Replace this literal with a type name - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.stderr deleted file mode 100644 index fd46b473574..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::fn-generics-with-type-annotation]: Function generics with type annotation - ╭▸ -7 │ "#type": "Int" - │ ━━━ Remove this type annotation - │ - ├ help: Function generics declarations cannot have type annotations. Generic parameter lists define type parameters for the function, and do not have a meaningful type themselves. - ╰ note: In the 'fn' special form, the generics argument should be either a tuple of identifiers such as (T, U) or a struct of bounded type parameters such as (T: SomeBound, U: OtherBound, V: _), where an underscore indicates no bound. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.stdout deleted file mode 100644 index a6dbf78c6e5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct-underscore.stdout +++ /dev/null @@ -1,25 +0,0 @@ -Expr#4294967040@32 - ExprKind (Closure) - ClosureExpr#4294967040@32 - ClosureSignature#4294967040@32 - Generics#4294967040@13 - GenericParam#4294967040@12 (name: T) - Type#4294967040@18 - TypeKind (Infer) - Expr#4294967040@31 - ExprKind (Call) - CallExpr#4294967040@31 - Expr#4294967040@22 - ExprKind (Path) - Path#4294967040@22 (rooted: false) - PathSegment#4294967040@21 (name: +) - Argument#4294967040@26 - Expr#4294967040@26 - ExprKind (Path) - Path#4294967040@26 (rooted: false) - PathSegment#4294967040@25 (name: a) - Argument#4294967040@30 - Expr#4294967040@30 - ExprKind (Path) - Path#4294967040@30 (rooted: false) - PathSegment#4294967040@29 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.stdout deleted file mode 100644 index 6564626df75..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-struct.stdout +++ /dev/null @@ -1,29 +0,0 @@ -Expr#4294967040@34 - ExprKind (Closure) - ClosureExpr#4294967040@34 - ClosureSignature#4294967040@34 - Generics#4294967040@15 - GenericParam#4294967040@14 (name: T) - Type#4294967040@13 - TypeKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: Int) - Type#4294967040@20 - TypeKind (Infer) - Expr#4294967040@33 - ExprKind (Call) - CallExpr#4294967040@33 - Expr#4294967040@24 - ExprKind (Path) - Path#4294967040@24 (rooted: false) - PathSegment#4294967040@23 (name: +) - Argument#4294967040@28 - Expr#4294967040@28 - ExprKind (Path) - Path#4294967040@28 (rooted: false) - PathSegment#4294967040@27 (name: a) - Argument#4294967040@32 - Expr#4294967040@32 - ExprKind (Path) - Path#4294967040@32 (rooted: false) - PathSegment#4294967040@31 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.stderr deleted file mode 100644 index f850e33af93..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-literal.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-fn-generics-expression]: Invalid expression in function generics - ╭▸ -7 │ { "#literal": 1 } - │ ━━━━━━━━━━━━━━━━━ Use a valid generics expression - │ - ├ help: Function generics must be specified as either a tuple of identifiers or a struct of bounded type parameters. Other expression types are not valid in this context. - ╰ note: Valid generics expressions include: - - Empty: () - - Tuple of identifiers: (T, U, V) - - Struct with bounds: (T: SomeBound, U: OtherBound) or (T: _, U: _) for unbounded types \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.stderr deleted file mode 100644 index 59710edbc9a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-path.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-fn-generic-param]: Invalid generic parameter in function declaration - ╭▸ -7 │ "::core::math::add" - │ ━━━━━━━━━━━━━━━━━ Use a simple identifier here - │ - ├ help: Generic type parameters must be simple identifiers. Qualified paths or complex expressions cannot be used in this context. - ╰ note: In function generic parameter lists, each element must be a simple identifier. For example: (T, U, V) is valid, but (some::path,) is not. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.stderr deleted file mode 100644 index fd46b473574..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::fn-generics-with-type-annotation]: Function generics with type annotation - ╭▸ -7 │ "#type": "Int" - │ ━━━ Remove this type annotation - │ - ├ help: Function generics declarations cannot have type annotations. Generic parameter lists define type parameters for the function, and do not have a meaningful type themselves. - ╰ note: In the 'fn' special form, the generics argument should be either a tuple of identifiers such as (T, U) or a struct of bounded type parameters such as (T: SomeBound, U: OtherBound, V: _), where an underscore indicates no bound. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.stdout deleted file mode 100644 index b44dc9e33ae..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-generics-tuple.stdout +++ /dev/null @@ -1,25 +0,0 @@ -Expr#4294967040@31 - ExprKind (Closure) - ClosureExpr#4294967040@31 - ClosureSignature#4294967040@31 - Generics#4294967040@12 - GenericParam#4294967040@11 (name: T) - Type#4294967040@17 - TypeKind (Infer) - Expr#4294967040@30 - ExprKind (Call) - CallExpr#4294967040@30 - Expr#4294967040@21 - ExprKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: +) - Argument#4294967040@25 - Expr#4294967040@25 - ExprKind (Path) - Path#4294967040@25 (rooted: false) - PathSegment#4294967040@24 (name: a) - Argument#4294967040@29 - Expr#4294967040@29 - ExprKind (Path) - Path#4294967040@29 (rooted: false) - PathSegment#4294967040@28 (name: b) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.stderr deleted file mode 100644 index bef8522b72f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-duplicates.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::duplicate-closure-parameter]: Duplicate closure parameter - ╭▸ -6 │ { "#struct": { "a": "Int", "a": "Int" } }, - │ ┬ ━ Remove this duplicate parameter 'a' - │ │ - │ 'a' was previously declared here - │ - ├ help: Each function parameter must have a unique name. Rename this parameter or remove the duplicate declaration. - ╰ note: Function parameters must have unique names within the same parameter list. For example, in fn(x: Int, y: String): ReturnType body), 'x' and 'y' are unique parameters. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.stderr deleted file mode 100644 index ccf6c0698d2..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-tuple.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-fn-params-expression]: Invalid expression in function parameters - ╭▸ -6 │ { "#tuple": [] }, - │ ━━━━━━━━━━━━━━━━ Use a struct expression for parameters - │ - ├ help: Function parameters must be specified as a struct where field names are parameter names and field values are parameter types. Other expression types are not valid in this context. - ╰ note: Valid parameter expression is a struct in the form: (param1: Type1, param2: Type2, ...) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.stderr deleted file mode 100644 index 59ea7e39a8b..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/fn-params-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::fn-params-with-type-annotation]: Function parameters with type annotation - ╭▸ -8 │ "#type": "Int" - │ ━━━ Remove this type annotation - │ - ├ help: Function parameter declarations cannot have type annotations at the struct level. The struct itself represents the parameter list, and each field represents a parameter with its type. - ╰ note: In the 'fn' special form, parameter lists should be structured as (param1: Type1, param2: Type2), where the struct itself does not have a type annotation. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.jsonc deleted file mode 100644 index bf4d315b293..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -//@ run: fail -//@ description: If any generics are used in the path, error out -["::kernel::special_form::if"] -//~^ERROR Remove these generic arguments diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.stderr deleted file mode 100644 index b6364489393..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/generics.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::unknown-special-form]: Unknown special form - ╭▸ -3 │ ["::kernel::special_form::if"] - │ ┯ ─ ... and these too - │ │ - │ Remove these generic arguments - │ - ├ help: Special form paths must not include generic arguments. Remove the angle brackets and their contents. - ╰ note: Special forms are built-in language constructs that don't support generics in their path reference. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.stderr deleted file mode 100644 index 021e4df6878..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-1.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::if", { "#literal": true }] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: Use either: - │ - if/2: (if condition then-expr) - │ - if/3: (if condition then-expr else-expr) - ╰ note: The if function has 2 variants: if/2, if/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.stderr deleted file mode 100644 index ce3184d2fdd..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/if-4.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::if", - 5 │ │ { "#literal": true }, - 6 │ │ { "#literal": true }, - 7 │ │ { "#literal": true }, - 8 │ │ { "#literal": true } - │ │ ━━━━━━━━━━━━━━━━━━━━ Remove this argument - 9 │ │ //~^ ERROR Remove this argument -10 │ │ ] - │ └─┘ In this `if` special form call - │ - ├ help: Use either: - │ - if/2: (if condition then-expr) - │ - if/3: (if condition then-expr else-expr) - ╰ note: The if function has 2 variants: if/2, if/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.stderr deleted file mode 100644 index 0dc6d995330..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-1.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::index", "x"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The index/2 form should look like: ([] object index) - ╰ note: The index function has 1 variant: index/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.jsonc deleted file mode 100644 index faf162f1964..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: index/2 should compile -["::kernel::special_form::index", "x", "y"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.stdout deleted file mode 100644 index a8743f1e0d9..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-2.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Expr#4294967040@16 - ExprKind (Index) - IndexExpr#4294967040@16 - Expr#4294967040@11 - ExprKind (Path) - Path#4294967040@11 (rooted: false) - PathSegment#4294967040@10 (name: x) - Expr#4294967040@15 - ExprKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: y) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.stderr deleted file mode 100644 index dd36e85cc50..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/index-3.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ┌ [ -4 │ │ "::kernel::special_form::index", -5 │ │ "x", -6 │ │ "y", -7 │ │ "z" - │ │ ━ Remove this argument -8 │ │ //~^ ERROR Remove this argument -9 │ │ ] - │ └─┘ In this `index` special form call - │ - ├ help: The index/2 form should look like: ([] object index) - ╰ note: The index function has 1 variant: index/2 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.stderr deleted file mode 100644 index 7af3596c72c..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-1.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::input", "x"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: Use either: - │ - input/2: (input name type) - │ - input/3: (input name type default) - ╰ note: The input function has 2 variants: input/2, input/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.stdout deleted file mode 100644 index c0e604507a7..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-2.stdout +++ /dev/null @@ -1,7 +0,0 @@ -Expr#4294967040@16 - ExprKind (Input) - InputExpr#4294967040@16 (name: x) - Type#4294967040@15 - TypeKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: Y) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.jsonc deleted file mode 100644 index 89387c2fd28..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: input/3 should compile -["::kernel::special_form::input", "x", "Y", "z"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.stdout deleted file mode 100644 index dae82e38da3..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-3.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Expr#4294967040@20 - ExprKind (Input) - InputExpr#4294967040@20 (name: x) - Type#4294967040@15 - TypeKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: Y) - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: z) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.stderr deleted file mode 100644 index ffbf5b0443a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/input-4.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::input", - 5 │ │ "x", - 6 │ │ "Y", - 7 │ │ "z", - 8 │ │ "w" - │ │ ━ Remove this argument - 9 │ │ //~^ ERROR Remove this argument -10 │ │ ] - │ └─┘ In this `input` special form call - │ - ├ help: Use either: - │ - input/2: (input name type) - │ - input/3: (input name type default) - ╰ note: The input function has 2 variants: input/2, input/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.stderr deleted file mode 100644 index dd9cea6aaaa..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/labeled-argument.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[special-form-expander::labeled-arguments-not-supported]: Labeled arguments not supported in special forms - ╭▸ - 3 │ ┏ [ - 4 │ ┃ "::kernel::special_form::if", - 5 │ ┃ { - 6 │ ┃ ":test": { "#literal": true } - │ ┃ ───────────────────────────── Remove this labeled argument - ‡ ┃ -10 │ ┃ { "#literal": false } -11 │ ┃ ] - │ ┗━┛ In this special form call - │ - ├ help: Special forms only accept positional arguments. Convert all labeled arguments to positional arguments in the correct order. - ╰ note: Unlike regular functions, special forms have fixed parameter positions and cannot use labeled arguments. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.stderr deleted file mode 100644 index 0f02728fe12..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-2.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::let", "x", { "#literal": true }] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: Use either: - │ - let/3: (let name value body) - │ - let/4: (let name type value body) - ╰ note: The let function has 2 variants: let/3, let/4 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.stderr deleted file mode 100644 index f131e8a6b9b..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-5.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::let", - 5 │ │ "x", - 6 │ │ "Boolean", - ‡ │ - 9 │ │ { "#literal": true } - │ │ ━━━━━━━━━━━━━━━━━━━━ Remove this argument -10 │ │ //~^ ERROR Remove this argument -11 │ │ ] - │ └─┘ In this `let` special form call - │ - ├ help: Use either: - │ - let/3: (let name value body) - │ - let/4: (let name type value body) - ╰ note: The let function has 2 variants: let/3, let/4 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.jsonc deleted file mode 100644 index 1fb3ee07d9a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -//@ run: fail -//@ description: The binding name must be a identifier -[ - "::kernel::special_form::let", - "Int", - //~^ ERROR Replace this with a simple identifier - { "#literal": true }, - [ - "::kernel::special_form::let", - "x::A", - //~^ ERROR Replace this with a simple identifier - "y::B", - [ - "::kernel::special_form::let", - "::z", - //~^ ERROR Replace this with a simple identifier - { "#literal": true }, - ["+", "x", "y"] - ] - ] -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.stderr deleted file mode 100644 index 7811060c3f2..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-ident.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -5 │ "Int", - │ ━━━━━━ Replace this with a simple identifier - │ - ├ help: let binding names must be simple identifiers without any path qualification. Qualified paths cannot be used as binding names. - ╰ note: Valid identifiers are simple names like 'x', 'counter', '+', or 'user_name' without any namespace qualification, generic parameters, or path separators. - -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -10 │ "x::A", - │ ━━━━ Replace this with a simple identifier - │ - ├ help: let binding names must be simple identifiers without any path qualification. Qualified paths cannot be used as binding names. - ╰ note: Valid identifiers are simple names like 'x', 'counter', '+', or 'user_name' without any namespace qualification, generic parameters, or path separators. - -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -15 │ "::z", - │ ━━━ Replace this with a simple identifier - │ - ├ help: let binding names must be simple identifiers without any path qualification. Qualified paths cannot be used as binding names. - ╰ note: Valid identifiers are simple names like 'x', 'counter', '+', or 'user_name' without any namespace qualification, generic parameters, or path separators. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.stderr deleted file mode 100644 index 52f710f5242..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-argument-not-path.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-binding-name]: Invalid binding name - ╭▸ -5 │ { "#literal": true }, - │ ━━━━━━━━━━━━━━━━━━━━ Replace this expression with a simple identifier - │ - ├ help: The let binding name must be a simple identifier. Complex expressions are not allowed in binding positions. - ╰ note: Valid examples of let bindings: - - (let x value body) - - (let counter 0 ...) - - (let user_name input ...) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.stderr deleted file mode 100644 index cf7fe7dbedb..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/let-collect-all-errors.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -7 │ { "#literal": 1 }, - │ ━━━━━━━━━━━━━━━━━ Replace this literal with a type name - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -5 │ "::x", - │ ━━━ Replace this with a simple identifier - │ - ├ help: let binding names must be simple identifiers without any path qualification. Qualified paths cannot be used as binding names. - ╰ note: Valid identifiers are simple names like 'x', 'counter', '+', or 'user_name' without any namespace qualification, generic parameters, or path separators. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.stderr deleted file mode 100644 index 65f1e2c06c0..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-2.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::newtype", "x", "Integer"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The newtype/3 form should look like: (newtype name type-expr body) - ╰ note: The newtype function has 1 variant: newtype/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.stderr deleted file mode 100644 index 341f8c2b023..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-4.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::newtype", - 5 │ │ "x", - 6 │ │ "Boolean", - 7 │ │ ["+", "x", "x"], - 8 │ │ { "#literal": true } - │ │ ━━━━━━━━━━━━━━━━━━━━ Remove this argument - 9 │ │ //~^ ERROR Remove this argument -10 │ │ ] - │ └─┘ In this `newtype` special form call - │ - ├ help: The newtype/3 form should look like: (newtype name type-expr body) - ╰ note: The newtype function has 1 variant: newtype/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.stderr deleted file mode 100644 index 5fd593c50af..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/newtype-collect-all-errors.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] - │ ━━━━━━━━━━━━━━━━━ Replace this literal with a type name - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -3 │ ["::kernel::special_form::newtype", "::x", { "#literal": 1 }, "x"] - │ ━━━ Replace this qualified path with a simple identifier - │ - ├ help: The newtype binding requires a simple type name (like 'String' or 'MyType'), not a qualified path (like 'std::string::String'). Remove the path segments. - ╰ note: Valid type names are simple identifiers, optionally followed by generic arguments (e.g., 'Identifier' or 'Container'). They cannot contain '::' path separators in this context. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.stderr deleted file mode 100644 index 89f6f1e4fba..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/not-enough-segments.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::unknown-special-form]: Unknown special form - ╭▸ -4 │ "::kernel::special_form", - │ ━━━━━━━━━━━━━━━━━━━━━━ Fix this path to have exactly 3 segments - │ - ├ help: Special form paths must follow the pattern '::kernel::special_form::' with exactly 3 segments - ╰ note: Found path with 2 segments, but special form paths must have exactly 3 segments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.stderr deleted file mode 100644 index 838742a7b6e..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/too-many-segments.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::unknown-special-form]: Unknown special form - ╭▸ -4 │ "::kernel::special_form::if::extra", - │ ┯━━━━━━━━━━━━━━━━━━━━━━━━━━━┬──── - │ │ │ - │ │ Remove this extra segment - │ Fix this path to have exactly 3 segments - │ - ├ help: Special form paths must follow the pattern '::kernel::special_form::' with exactly 3 segments - ╰ note: Found path with 4 segments, but special form paths must have exactly 3 segments \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.stderr deleted file mode 100644 index 888ac83beb7..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-2.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::type", "x", "Integer"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The type/3 form should look like: (type name type-expr body) - ╰ note: The type function has 1 variant: type/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.stdout deleted file mode 100644 index 558a4cd094c..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-3.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Expr#4294967040@20 - ExprKind (Type) - TypeExpr#4294967040@20 (name: x) - Type#4294967040@15 - TypeKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: Integer) - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.stderr deleted file mode 100644 index 0213de44b0e..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-4.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::type", - 5 │ │ "x", - 6 │ │ "Boolean", - 7 │ │ ["+", "x", "x"], - 8 │ │ { "#literal": true } - │ │ ━━━━━━━━━━━━━━━━━━━━ Remove this argument - 9 │ │ //~^ ERROR Remove this argument -10 │ │ ] - │ └─┘ In this `type` special form call - │ - ├ help: The type/3 form should look like: (type name type-expr body) - ╰ note: The type function has 1 variant: type/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.stderr deleted file mode 100644 index fb55601b5b0..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-collect-all-errors.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] - │ ━━━━━━━━━━━━━━━━━ Replace this literal with a type name - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::qualified-binding-name]: Qualified path used as binding name - ╭▸ -3 │ ["::kernel::special_form::type", "::x", { "#literal": 1 }, "x"] - │ ━━━ Replace this qualified path with a simple identifier - │ - ├ help: The type binding requires a simple type name (like 'String' or 'MyType'), not a qualified path (like 'std::string::String'). Remove the path segments. - ╰ note: Valid type names are simple identifiers, optionally followed by generic arguments (e.g., 'Identifier' or 'Container'). They cannot contain '::' path separators in this context. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.stderr deleted file mode 100644 index a2318ee60aa..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-arbitrary.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -6 │ ["::core::math::headpat", "Int", "Float"] - │ ━━━━━━━━━━━━━━━━━━━━━ This function cannot be used as a type constructor - │ - ├ help: Only specific type constructors like intersection (&) and union (|) operators can be used in type expressions. - ╰ note: Currently supported type operations are: - - Intersection: math::bit_and (written as & in source) - - Union: math::bit_or (written as | in source) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.jsonc deleted file mode 100644 index 37373f2ce09..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -//@ run: pass -//@ description: Invoking `::core::bits::and` should convert into a intersection type expression -[ - "::kernel::special_form::as", - { "#literal": true }, - ["::core::bits::and", "Int", "Float"] -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.stdout deleted file mode 100644 index 8803f54ebc5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-intersection.stdout +++ /dev/null @@ -1,18 +0,0 @@ -Expr#4294967040@27 - ExprKind (As) - AsExpr#4294967040@27 - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (True) - Type#4294967040@26 - TypeKind (Intersection) - IntersectionType#4294967040@26 - Type#4294967040@21 - TypeKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: Int) - Type#4294967040@25 - TypeKind (Path) - Path#4294967040@25 (rooted: false) - PathSegment#4294967040@24 (name: Float) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.stderr deleted file mode 100644 index cca84924ac3..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-not-path.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -7 │ ["+", { "#literal": 1 }, { "#literal": 2 }], - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Function call with non-path callee cannot be used as a type - │ - ├ help: Only specific type constructors like intersection (&) and union (|) operators can be used in type expressions. - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.jsonc deleted file mode 100644 index 5eb86c1dbe5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -//@ run: pass -//@ description: Invoking `::core::bits::or` should convert into a union type expression -[ - "::kernel::special_form::as", - { "#literal": true }, - ["::core::bits::or", "Int", "Float"] -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.stdout deleted file mode 100644 index 37f72288b1d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-call-union.stdout +++ /dev/null @@ -1,18 +0,0 @@ -Expr#4294967040@27 - ExprKind (As) - AsExpr#4294967040@27 - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (True) - Type#4294967040@26 - TypeKind (Union) - UnionType#4294967040@26 - Type#4294967040@21 - TypeKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: Int) - Type#4294967040@25 - TypeKind (Path) - Path#4294967040@25 (rooted: false) - PathSegment#4294967040@24 (name: Float) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.jsonc deleted file mode 100644 index 7059285a0d8..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.jsonc +++ /dev/null @@ -1,24 +0,0 @@ -//@ run: fail -//@ description: every expression, except for struct, tuple and paths are not valid type expressions -[ - "::kernel::special_form::as", - [ - "::kernel::special_form::as", - [ - "::kernel::special_form::as", - [ - "::kernel::special_form::as", - "x", - // ensure that we properly resolve nested is expressions as errors - ["::kernel::special_form::as", "x", "X"] - //~^ ERROR Replace this as with a proper type expression - ], - { "#literal": 1 } - //~^ ERROR Replace this literal with a type name - ], - { "#list": ["a", "b"] } - //~^ ERROR Replace this list with a proper type expression - ], - { "#dict": { "a": "b" } } - //~^ ERROR Replace this dictionary with a proper type expression -] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.stderr deleted file mode 100644 index e14b4e82e4f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-invalid.stderr +++ /dev/null @@ -1,55 +0,0 @@ -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -22 │ { "#dict": { "a": "b" } } - │ ━━━━━━━━━━━━━━━━━━━━━━━━━ Replace this dictionary with a proper type expression - │ - ├ help: Dictionaries do not constitute a valid type expression, did you mean to instantiate a struct type or refer to the `Dict` type? - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -16 │ { "#literal": 1 } - │ ━━━━━━━━━━━━━━━━━ Replace this literal with a type name - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -19 │ { "#list": ["a", "b"] } - │ ━━━━━━━━━━━━━━━━━━━━━━━ Replace this list with a proper type expression - │ - ├ help: Arrays do not constitute a valid type expression, did you mean to instantiate a tuple type or refer to the `Array` type? - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option - -error[special-form-expander::invalid-type-expression]: Invalid type expression - ╭▸ -13 │ ["::kernel::special_form::as", "x", "X"] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Replace this as with a proper type expression - │ - ├ help: Replace this expression with a valid type reference, struct type, or tuple type - ╰ note: Valid type expressions include: - - Type names: String, Int, Float - - Struct types: {name: String, age: Int} - - Tuple types: (String, Int, Boolean) - - Unions: (| String Int) - - Intersections: (& String Int) - - Generic types: Array, Option \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.stdout deleted file mode 100644 index 5342d407cc1..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-struct.stdout +++ /dev/null @@ -1,20 +0,0 @@ -Expr#4294967040@26 - ExprKind (As) - AsExpr#4294967040@26 - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (True) - Type#4294967040@24 - TypeKind (Struct) - StructType#4294967040@24 - StructField#4294967040@16 (name: name) - Type#4294967040@15 - TypeKind (Path) - Path#4294967040@15 (rooted: false) - PathSegment#4294967040@14 (name: Name) - StructField#4294967040@23 (name: birthday) - Type#4294967040@22 - TypeKind (Path) - Path#4294967040@22 (rooted: false) - PathSegment#4294967040@21 (name: Date) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.stdout deleted file mode 100644 index 87f6ecbbdfb..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-expr-tuple.stdout +++ /dev/null @@ -1,20 +0,0 @@ -Expr#4294967040@20 - ExprKind (As) - AsExpr#4294967040@20 - Expr#4294967040@9 - ExprKind (Literal) - LiteralExpr#4294967040@8 - Primitive (True) - Type#4294967040@18 - TypeKind (Tuple) - TupleType#4294967040@18 - TupleField#4294967040@13 - Type#4294967040@13 - TypeKind (Path) - Path#4294967040@13 (rooted: false) - PathSegment#4294967040@12 (name: Name) - TupleField#4294967040@17 - Type#4294967040@17 - TypeKind (Path) - Path#4294967040@17 (rooted: false) - PathSegment#4294967040@16 (name: Date) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.stderr deleted file mode 100644 index 641a2562e1b..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-absolute-path.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-generic-argument-path]: Invalid path in generic argument - ╭▸ -5 │ "Foo<::foo::Bar>", - │ ━━━━━━━━━━ Replace with a simple identifier - │ - ├ help: Generic arguments must be simple identifiers. Qualified paths cannot be used as generic arguments in this context. - ╰ note: In generic parameter constraints, arguments should be simple identifiers like 'T', 'U', or 'Element' without namespace qualification or path separators. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.stderr deleted file mode 100644 index d3c67b6aabe..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-duplicate.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[special-form-expander::duplicate-generic-constraint]: Duplicate generic parameter constraint - ╭▸ -5 │ "Foo", - │ ┬ ━ Remove this duplicate declaration of 'T' - │ │ - │ 'T' was previously declared here - │ - ├ help: Each generic parameter can only be declared once in a function or type definition. Remove the duplicate declaration or use a different name. - ╰ note: Generic parameter names must be unique within a single generic parameter list. For example, in foo, 'T' and 'U' are unique parameters. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.stderr deleted file mode 100644 index 7fc76ef1683..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-generic-ident.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-generic-argument-path]: Invalid path in generic argument - ╭▸ -5 │ "Foo>", - │ ━━━━━━━━ Replace with a simple identifier - │ - ├ help: Generic arguments must be simple identifiers. Qualified paths cannot be used as generic arguments in this context. - ╰ note: In generic parameter constraints, arguments should be simple identifiers like 'T', 'U', or 'Element' without namespace qualification or path separators. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.stderr deleted file mode 100644 index 6a87a800337..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-relative-path.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-generic-argument-path]: Invalid path in generic argument - ╭▸ -5 │ "Foo", - │ ━━━━━━━━ Replace with a simple identifier - │ - ├ help: Generic arguments must be simple identifiers. Qualified paths cannot be used as generic arguments in this context. - ╰ note: In generic parameter constraints, arguments should be simple identifiers like 'T', 'U', or 'Element' without namespace qualification or path separators. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.stderr deleted file mode 100644 index 96efe0878e6..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-generic-struct.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-generic-argument-type]: Invalid type in generic argument - ╭▸ -5 │ "Foo<(a: String)>", - │ ━━━━━━━━━━━ Use a simple type identifier here - │ - ├ help: Generic argument types must be simple path identifiers. Complex types like structs, tuples, or function types cannot be used as generic argument types in this context. - ╰ note: Valid generic argument types are simple identifiers that refer to type names, such as 'String', 'Number', or type parameters like 'T'. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.stdout deleted file mode 100644 index 9eb3b48279b..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-infer.stdout +++ /dev/null @@ -1,9 +0,0 @@ -Expr#4294967040@18 - ExprKind (Type) - TypeExpr#4294967040@18 (name: x) - Type#4294967040@13 - TypeKind (Infer) - Expr#4294967040@17 - ExprKind (Path) - Path#4294967040@17 (rooted: false) - PathSegment#4294967040@16 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.stdout deleted file mode 100644 index 22c88eadab2..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-never.stdout +++ /dev/null @@ -1,13 +0,0 @@ -Expr#4294967040@24 - ExprKind (Type) - TypeExpr#4294967040@24 (name: x) - Type#4294967040@19 - TypeKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@14 (name: kernel) - PathSegment#4294967040@16 (name: type) - PathSegment#4294967040@18 (name: Never) - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.stderr deleted file mode 100644 index cc0a2673554..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-struct-has-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::type-with-existing-annotation]: Type expression with redundant type annotation - ╭▸ -8 │ "#type": "(name: String)" - │ ━━━━━━━━━━━━━━ Remove this type annotation - │ - ├ help: Type expressions used in special forms cannot have their own type annotations. The expression itself defines a type and cannot be annotated with another type. - ╰ note: When constructing type expressions for special forms like 'type', 'newtype', or 'as', the expression itself represents a type definition and cannot have a separate type annotation. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.stderr deleted file mode 100644 index 383301502c5..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-tuple-has-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::type-with-existing-annotation]: Type expression with redundant type annotation - ╭▸ -8 │ "#type": "(Int)" - │ ━━━━━ Remove this type annotation - │ - ├ help: Type expressions used in special forms cannot have their own type annotations. The expression itself defines a type and cannot be annotated with another type. - ╰ note: When constructing type expressions for special forms like 'type', 'newtype', or 'as', the expression itself represents a type definition and cannot have a separate type annotation. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.stdout deleted file mode 100644 index 5fe422afeb6..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/type-unknown.stdout +++ /dev/null @@ -1,13 +0,0 @@ -Expr#4294967040@24 - ExprKind (Type) - TypeExpr#4294967040@24 (name: x) - Type#4294967040@19 - TypeKind (Path) - Path#4294967040@19 (rooted: true) - PathSegment#4294967040@14 (name: kernel) - PathSegment#4294967040@16 (name: type) - PathSegment#4294967040@18 (name: Unknown) - Expr#4294967040@23 - ExprKind (Path) - Path#4294967040@23 (rooted: false) - PathSegment#4294967040@22 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.stderr deleted file mode 100644 index 768da72de27..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form-typo.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::unknown-special-form]: Unknown special form - ╭▸ -3 │ ["::kernel::special_form::nuwtype"] - │ ┬───────────────────────┯━━━━━━ - │ │ │ - │ │ Replace 'nuwtype' with a valid special form name - │ This special form path is invalid - │ - ├ help: Did you mean to use 'newtype' instead? - ╰ note: Available special forms include: if, as, let, type, newtype, use, fn, input, access, index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.stderr deleted file mode 100644 index ccf72b67d02..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/unknown-special-form.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::unknown-special-form]: Unknown special form - ╭▸ -3 │ ["::kernel::special_form::headpat"] - │ ┬───────────────────────┯━━━━━━ - │ │ │ - │ │ Replace 'headpat' with a valid special form name - │ This special form path is invalid - │ - ├ help: Special forms must use one of the predefined names shown in the note below - ╰ note: Available special forms include: if, as, let, type, newtype, use, fn, input, access, index \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.stderr deleted file mode 100644 index b8479ec9ebc..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-2.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ -3 │ ["::kernel::special_form::use", "::math", { "#tuple": ["pi"] }] - │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Add missing arguments - │ - ├ help: The use/3 form should look like: (use module imports body) - ╰ note: The use function has 1 variant: use/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.jsonc deleted file mode 100644 index 94b7e13b585..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: use/3 should compile to AST node -["::kernel::special_form::use", "::math", { "#tuple": ["pi"] }, "x"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.stdout deleted file mode 100644 index 02d9613cb16..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-3.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Expr#4294967040@22 - ExprKind (Use) - UseExpr#4294967040@22 - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: math) - UseKind (Named) - UseBinding#4294967040@15 (name: pi) - Expr#4294967040@21 - ExprKind (Path) - Path#4294967040@21 (rooted: false) - PathSegment#4294967040@20 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.stderr deleted file mode 100644 index fe93229b352..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-4.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[special-form-expander::special-form-argument-length]: Incorrect number of arguments for special form - ╭▸ - 3 │ ┌ [ - 4 │ │ "::kernel::special_form::use", - 5 │ │ "::math", - 6 │ │ { "#tuple": ["pi"] }, - 7 │ │ "x", - 8 │ │ "y" - │ │ ━ Remove this argument - 9 │ │ //~^ ERROR Remove this argument -10 │ │ ] - │ └─┘ In this `use` special form call - │ - ├ help: The use/3 form should look like: (use module imports body) - ╰ note: The use function has 1 variant: use/3 \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.stderr deleted file mode 100644 index 9624214648a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-ident.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ "::core::math::X", - │ ━━━━━━━━━━━━━━━ Use a simple identifier here - │ - ├ help: Use binding names must be simple identifiers. Qualified paths or complex expressions cannot be used in this context. - ╰ note: In tuple imports, each element must be a simple identifier. For example: (name1, name2) is valid, but (path::to::name,) is not. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.stderr deleted file mode 100644 index d7465879c9f..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob-not-star.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ "X", - │ ━ Replace with a valid import expression - │ - ├ help: Use imports must be either a glob (*), a tuple of identifiers, or a struct of bindings. Other expression types are not valid in this context. - ╰ note: Valid import expressions include: - - Glob: * - - Tuple of identifiers: (name1, name2) - - Struct with aliases: (name1: alias1, name2: alias2) or (name1: _, name2: _) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.jsonc deleted file mode 100644 index 160fef929c9..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: glob pattern should compile to AST node -["::kernel::special_form::use", "::math", "*", "x"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.stdout deleted file mode 100644 index 38edb64772c..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-glob.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Expr#4294967040@20 - ExprKind (Use) - UseExpr#4294967040@20 - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: math) - UseKind (Glob) - Glob#4294967040@13 - Expr#4294967040@19 - ExprKind (Path) - Path#4294967040@19 (rooted: false) - PathSegment#4294967040@18 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.stderr deleted file mode 100644 index ce1165be228..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-literal.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ { "#literal": 1 }, - │ ━━━━━━━━━━━━━━━━━ Replace with a valid import expression - │ - ├ help: Use imports must be either a glob (*), a tuple of identifiers, or a struct of bindings. Other expression types are not valid in this context. - ╰ note: Valid import expressions include: - - Glob: * - - Tuple of identifiers: (name1, name2) - - Struct with aliases: (name1: alias1, name2: alias2) or (name1: _, name2: _) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.stderr deleted file mode 100644 index e7124de25aa..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-literal.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -8 │ "x": { "#literal": 1 } - │ ━━━━━━━━━━━━━━━━━ Replace with a valid import expression - │ - ├ help: Use imports must be either a glob (*), a tuple of identifiers, or a struct of bindings. Other expression types are not valid in this context. - ╰ note: Valid import expressions include: - - Glob: * - - Tuple of identifiers: (name1, name2) - - Struct with aliases: (name1: alias1, name2: alias2) or (name1: _, name2: _) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.stderr deleted file mode 100644 index 6e48cee9614..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-path.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -8 │ "x": "::core::math::sub" - │ ━━━━━━━━━━━━━━━━━ Use a simple identifier here - │ - ├ help: Use binding names must be simple identifiers. Qualified paths or complex expressions cannot be used in this context. - ╰ note: In tuple imports, each element must be a simple identifier. For example: (name1, name2) is valid, but (path::to::name,) is not. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.stderr deleted file mode 100644 index 7436e775433..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -8 │ "#type": "X" - │ ━ Remove this type annotation - │ - ├ help: Use import expressions cannot have type annotations. Import expressions define which symbols to import, and do not have a meaningful type in this context. - ╰ note: Import expressions in the 'use' special form can only be a glob (*), a tuple of identifiers, or a struct of bindings, none of which should have type annotations. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.stdout deleted file mode 100644 index 5ab2ccc759a..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-struct.stdout +++ /dev/null @@ -1,12 +0,0 @@ -Expr#4294967040@30 - ExprKind (Use) - UseExpr#4294967040@30 - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: math) - UseKind (Named) - UseBinding#4294967040@18 (name: x, alias: y) - UseBinding#4294967040@23 (name: z) - Expr#4294967040@29 - ExprKind (Path) - Path#4294967040@29 (rooted: false) - PathSegment#4294967040@28 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.stderr deleted file mode 100644 index 41efec6a33e..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-literal.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ { "#tuple": [{ "#literal": 1 }] }, - │ ━━━━━━━━━━━━━━━━━ Replace with a valid import expression - │ - ├ help: Use imports must be either a glob (*), a tuple of identifiers, or a struct of bindings. Other expression types are not valid in this context. - ╰ note: Valid import expressions include: - - Glob: * - - Tuple of identifiers: (name1, name2) - - Struct with aliases: (name1: alias1, name2: alias2) or (name1: _, name2: _) \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.stderr deleted file mode 100644 index f2397aedf24..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-path.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ { "#tuple": ["::core::math::sub"] }, - │ ━━━━━━━━━━━━━━━━━ Use a simple identifier here - │ - ├ help: Use binding names must be simple identifiers. Qualified paths or complex expressions cannot be used in this context. - ╰ note: In tuple imports, each element must be a simple identifier. For example: (name1, name2) is valid, but (path::to::name,) is not. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.stderr b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.stderr deleted file mode 100644 index 3c0516f91e4..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple-type.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[special-form-expander::invalid-use-import]: Invalid use import expression - ╭▸ -6 │ { "#tuple": ["::core::math::sub"], "#type": "Number" }, - │ ━━━━━━ Remove this type annotation - │ - ├ help: Use import expressions cannot have type annotations. Import expressions define which symbols to import, and do not have a meaningful type in this context. - ╰ note: Import expressions in the 'use' special form can only be a glob (*), a tuple of identifiers, or a struct of bindings, none of which should have type annotations. \ No newline at end of file diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.jsonc b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.jsonc deleted file mode 100644 index 95414083bac..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.jsonc +++ /dev/null @@ -1,3 +0,0 @@ -//@ run: pass -//@ description: tuple pattern should compile to AST node -["::kernel::special_form::use", "::math", { "#tuple": ["x", "z"] }, "x"] diff --git a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.stdout b/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.stdout deleted file mode 100644 index 92992865d7d..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/special-form-expander/use-tuple.stdout +++ /dev/null @@ -1,12 +0,0 @@ -Expr#4294967040@26 - ExprKind (Use) - UseExpr#4294967040@26 - Path#4294967040@11 (rooted: true) - PathSegment#4294967040@10 (name: math) - UseKind (Named) - UseBinding#4294967040@15 (name: x) - UseBinding#4294967040@19 (name: z) - Expr#4294967040@25 - ExprKind (Path) - Path#4294967040@25 (rooted: false) - PathSegment#4294967040@24 (name: x) diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/.spec.toml deleted file mode 100644 index 953a91550b6..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/type-extractor" diff --git a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/.spec.toml b/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/.spec.toml deleted file mode 100644 index 5c274519246..00000000000 --- a/libs/@local/hashql/ast/tests/ui/lowering/type-extractor/definition/.spec.toml +++ /dev/null @@ -1 +0,0 @@ -suite = "ast/lowering/type-definition-extractor" diff --git a/libs/@local/hashql/compiletest/src/pipeline.rs b/libs/@local/hashql/compiletest/src/pipeline.rs index 1e0696810b7..b62afd7efbe 100644 --- a/libs/@local/hashql/compiletest/src/pipeline.rs +++ b/libs/@local/hashql/compiletest/src/pipeline.rs @@ -153,11 +153,12 @@ impl<'heap> Pipeline<'heap> { bind_tri!(&mut self.diagnostics); let registry = ModuleRegistry::new(&self.env); - let types = tri!(hashql_ast::lowering::lower( + let types = tri!(hashql_ast::lower::lower( self.heap.intern_symbol("::main"), &mut expr, &self.env, ®istry, + &mut self.scratch )); let hir_interner = hashql_hir::intern::Interner::new(self.heap); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lower_expander.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_expander.rs new file mode 100644 index 00000000000..a0c75880545 --- /dev/null +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_expander.rs @@ -0,0 +1,76 @@ +use error_stack::ReportSink; +use hashql_ast::{ + self, format::SyntaxDump as _, lower::expander::Expander, node::expr::Expr, visit::Visitor as _, +}; +use hashql_core::{ + heap::Scratch, + module::{ModuleRegistry, namespace::ModuleNamespace}, + r#type::environment::Environment, +}; +use hashql_diagnostics::Diagnostic; + +use super::{RunContext, Suite, SuiteDiagnostic, SuiteDirectives, common::process_issues}; +use crate::harness::trial::TrialError; + +fn should_continue( + this: &impl Suite, + directives: &SuiteDirectives, + reports: &mut ReportSink, +) -> bool { + let Some(r#continue) = directives.get("continue") else { + return false; + }; + + let Some(r#continue) = r#continue.as_bool() else { + reports.capture(TrialError::Run( + this.name(), + "suite#continue must be a valid boolean", + )); + + return false; + }; + + r#continue +} + +pub(crate) struct AstLowerExpanderSuite; + +impl Suite for AstLowerExpanderSuite { + fn name(&self) -> &'static str { + "ast/lower/expander" + } + + fn description(&self) -> &'static str { + "Expansion of special forms in the AST" + } + + fn run<'heap>( + &self, + RunContext { + heap, + diagnostics, + suite_directives, + reports, + .. + }: RunContext<'_, 'heap>, + mut expr: Expr<'heap>, + ) -> Result { + let environment = Environment::new(heap); + let registry = ModuleRegistry::new(&environment); + let mut scratch = Scratch::new(); + + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); + + let mut expander = Expander::new(namespace, &mut scratch); + expander.visit_expr(&mut expr); + + if should_continue(self, suite_directives, reports) { + diagnostics.extend(expander.take_diagnostics().map(Diagnostic::boxed)); + } else { + process_issues(diagnostics, expander.take_diagnostics())?; + } + + Ok(expr.syntax_dump_to_string()) + } +} diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_node_mangler.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_node_mangler.rs similarity index 61% rename from libs/@local/hashql/compiletest/src/suite/ast_lowering_node_mangler.rs rename to libs/@local/hashql/compiletest/src/suite/ast_lower_node_mangler.rs index 71c88d4c570..d9a969eff60 100644 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_node_mangler.rs +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_node_mangler.rs @@ -1,21 +1,22 @@ use hashql_ast::{ format::SyntaxDump as _, - lowering::{ - name_mangler::NameMangler, pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, - }, + lower::{expander::Expander, name_mangler::NameMangler}, node::expr::Expr, visit::Visitor as _, }; -use hashql_core::{module::ModuleRegistry, r#type::environment::Environment}; +use hashql_core::{ + heap::Scratch, + module::{ModuleRegistry, namespace::ModuleNamespace}, + r#type::environment::Environment, +}; use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; -pub(crate) struct AstLoweringNameManglerSuite; +pub(crate) struct AstLowerNameManglerSuite; -impl Suite for AstLoweringNameManglerSuite { +impl Suite for AstLowerNameManglerSuite { fn name(&self) -> &'static str { - "ast/lowering/name-mangler" + "ast/lower/name-mangler" } fn description(&self) -> &'static str { @@ -31,14 +32,13 @@ impl Suite for AstLoweringNameManglerSuite { ) -> Result { let environment = Environment::new(heap); let registry = ModuleRegistry::new(&environment); + let mut scratch = Scratch::new(); - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); - let mut expander = SpecialFormExpander::new(heap); + let mut expander = Expander::new(namespace, &mut scratch); expander.visit_expr(&mut expr); - process_issues(diagnostics, expander.take_diagnostics())?; let mut renumberer = NameMangler::new(heap); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_node_renumberer.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_node_renumberer.rs similarity index 61% rename from libs/@local/hashql/compiletest/src/suite/ast_lowering_node_renumberer.rs rename to libs/@local/hashql/compiletest/src/suite/ast_lower_node_renumberer.rs index 16b4434370b..02b57ec9df9 100644 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_node_renumberer.rs +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_node_renumberer.rs @@ -1,21 +1,22 @@ use hashql_ast::{ format::SyntaxDump as _, - lowering::{ - node_renumberer::NodeRenumberer, pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, - }, + lower::{expander::Expander, node_renumberer::NodeRenumberer}, node::expr::Expr, visit::Visitor as _, }; -use hashql_core::{module::ModuleRegistry, r#type::environment::Environment}; +use hashql_core::{ + heap::Scratch, + module::{ModuleRegistry, namespace::ModuleNamespace}, + r#type::environment::Environment, +}; use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; -pub(crate) struct AstLoweringNodeRenumbererSuite; +pub(crate) struct AstLowerNodeRenumbererSuite; -impl Suite for AstLoweringNodeRenumbererSuite { +impl Suite for AstLowerNodeRenumbererSuite { fn name(&self) -> &'static str { - "ast/lowering/node-renumberer" + "ast/lower/node-renumberer" } fn description(&self) -> &'static str { @@ -31,14 +32,13 @@ impl Suite for AstLoweringNodeRenumbererSuite { ) -> Result { let environment = Environment::new(heap); let registry = ModuleRegistry::new(&environment); + let mut scratch = Scratch::new(); - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); - let mut expander = SpecialFormExpander::new(heap); + let mut expander = Expander::new(namespace, &mut scratch); expander.visit_expr(&mut expr); - process_issues(diagnostics, expander.take_diagnostics())?; let mut renumberer = NodeRenumberer::new(); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_sanitizer.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_sanitizer.rs similarity index 64% rename from libs/@local/hashql/compiletest/src/suite/ast_lowering_sanitizer.rs rename to libs/@local/hashql/compiletest/src/suite/ast_lower_sanitizer.rs index be48466b34a..520af4598d4 100644 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_sanitizer.rs +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_sanitizer.rs @@ -1,21 +1,22 @@ use hashql_ast::{ format::SyntaxDump as _, - lowering::{ - pre_expansion_name_resolver::PreExpansionNameResolver, sanitizer::Sanitizer, - special_form_expander::SpecialFormExpander, - }, + lower::{expander::Expander, sanitizer::Sanitizer}, node::expr::Expr, visit::Visitor as _, }; -use hashql_core::{module::ModuleRegistry, r#type::environment::Environment}; +use hashql_core::{ + heap::Scratch, + module::{ModuleRegistry, namespace::ModuleNamespace}, + r#type::environment::Environment, +}; use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; -pub(crate) struct AstLoweringSanitizerSuite; +pub(crate) struct AstLowerSanitizerSuite; -impl Suite for AstLoweringSanitizerSuite { +impl Suite for AstLowerSanitizerSuite { fn name(&self) -> &'static str { - "ast/lowering/sanitizer" + "ast/lower/sanitizer" } fn description(&self) -> &'static str { @@ -31,14 +32,13 @@ impl Suite for AstLoweringSanitizerSuite { ) -> Result { let environment = Environment::new(heap); let registry = ModuleRegistry::new(&environment); + let mut scratch = Scratch::new(); - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); - let mut expander = SpecialFormExpander::new(heap); + let mut expander = Expander::new(namespace, &mut scratch); expander.visit_expr(&mut expr); - process_issues(diagnostics, expander.take_diagnostics())?; let mut sanitizer = Sanitizer::new(); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_type_definition_extractor.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_type_definition_extractor.rs similarity index 76% rename from libs/@local/hashql/compiletest/src/suite/ast_lowering_type_definition_extractor.rs rename to libs/@local/hashql/compiletest/src/suite/ast_lower_type_definition_extractor.rs index 4072f547a2b..ca282a4155a 100644 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_type_definition_extractor.rs +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_type_definition_extractor.rs @@ -2,15 +2,14 @@ use core::fmt::Write as _; use hashql_ast::{ format::SyntaxDump as _, - lowering::{ - import_resolver::ImportResolver, name_mangler::NameMangler, - pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, type_extractor::TypeDefinitionExtractor, + lower::{ + expander::Expander, name_mangler::NameMangler, type_extractor::TypeDefinitionExtractor, }, node::expr::Expr, visit::Visitor as _, }; use hashql_core::{ + heap::Scratch, module::{ ModuleRegistry, locals::{Local, TypeDef}, @@ -23,11 +22,11 @@ use hashql_core::{ use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; use crate::suite::common::Annotated; -pub(crate) struct AstLoweringTypeDefinitionExtractorSuite; +pub(crate) struct AstLowerTypeDefinitionExtractorSuite; -impl Suite for AstLoweringTypeDefinitionExtractorSuite { +impl Suite for AstLowerTypeDefinitionExtractorSuite { fn name(&self) -> &'static str { - "ast/lowering/type-definition-extractor" + "ast/lower/type-definition-extractor" } fn description(&self) -> &'static str { @@ -43,23 +42,14 @@ impl Suite for AstLoweringTypeDefinitionExtractorSuite { ) -> Result { let environment = Environment::new(heap); let registry = ModuleRegistry::new(&environment); - - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); - - let mut expander = SpecialFormExpander::new(heap); - expander.visit_expr(&mut expr); - - process_issues(diagnostics, expander.take_diagnostics())?; + let mut scratch = Scratch::new(); let mut namespace = ModuleNamespace::new(®istry); namespace.import_prelude(); - let mut resolver = ImportResolver::new(heap, namespace); - resolver.visit_expr(&mut expr); - - process_issues(diagnostics, resolver.take_diagnostics())?; + let mut expander = Expander::new(namespace, &mut scratch); + expander.visit_expr(&mut expr); + process_issues(diagnostics, expander.take_diagnostics())?; let mut mangler = NameMangler::new(heap); mangler.visit_expr(&mut expr); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_type_extractor.rs b/libs/@local/hashql/compiletest/src/suite/ast_lower_type_extractor.rs similarity index 86% rename from libs/@local/hashql/compiletest/src/suite/ast_lowering_type_extractor.rs rename to libs/@local/hashql/compiletest/src/suite/ast_lower_type_extractor.rs index 1d0cc4c4de6..29a4073dfc1 100644 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_type_extractor.rs +++ b/libs/@local/hashql/compiletest/src/suite/ast_lower_type_extractor.rs @@ -2,18 +2,17 @@ use core::fmt::Write as _; use hashql_ast::{ format::SyntaxDump as _, - lowering::{ - import_resolver::ImportResolver, + lower::{ + expander::Expander, name_mangler::NameMangler, node_renumberer::NodeRenumberer, - pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, type_extractor::{TypeDefinitionExtractor, TypeExtractor}, }, node::expr::Expr, visit::Visitor as _, }; use hashql_core::{ + heap::Scratch, module::{ ModuleRegistry, locals::{Local, TypeDef}, @@ -26,11 +25,11 @@ use hashql_core::{ use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; use crate::suite::common::Annotated; -pub(crate) struct AstLoweringTypeExtractorSuite; +pub(crate) struct AstLowerTypeExtractorSuite; -impl Suite for AstLoweringTypeExtractorSuite { +impl Suite for AstLowerTypeExtractorSuite { fn name(&self) -> &'static str { - "ast/lowering/type-extractor" + "ast/lower/type-extractor" } fn description(&self) -> &'static str { @@ -46,22 +45,14 @@ impl Suite for AstLoweringTypeExtractorSuite { ) -> Result { let environment = Environment::new(heap); let registry = ModuleRegistry::new(&environment); - - let mut resolver = PreExpansionNameResolver::new(®istry); - resolver.visit_expr(&mut expr); - - let mut expander = SpecialFormExpander::new(heap); - expander.visit_expr(&mut expr); - - process_issues(diagnostics, expander.take_diagnostics())?; + let mut scratch = Scratch::new(); let mut namespace = ModuleNamespace::new(®istry); namespace.import_prelude(); - let mut resolver = ImportResolver::new(heap, namespace); - resolver.visit_expr(&mut expr); - - process_issues(diagnostics, resolver.take_diagnostics())?; + let mut expander = Expander::new(namespace, &mut scratch); + expander.visit_expr(&mut expr); + process_issues(diagnostics, expander.take_diagnostics())?; let mut mangler = NameMangler::new(heap); mangler.visit_expr(&mut expr); diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver.rs b/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver.rs deleted file mode 100644 index 7379ddfbb84..00000000000 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver.rs +++ /dev/null @@ -1,57 +0,0 @@ -use hashql_ast::{ - format::SyntaxDump as _, - lowering::{ - import_resolver::ImportResolver, pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, - }, - node::expr::Expr, - visit::Visitor as _, -}; -use hashql_core::{ - module::{ModuleRegistry, namespace::ModuleNamespace}, - r#type::environment::Environment, -}; - -use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; - -pub(crate) struct AstLoweringImportResolverSuite; - -impl Suite for AstLoweringImportResolverSuite { - fn name(&self) -> &'static str { - "ast/lowering/import-resolver" - } - - fn description(&self) -> &'static str { - "Import and module resolution in the AST" - } - - fn run<'heap>( - &self, - RunContext { - heap, diagnostics, .. - }: RunContext<'_, 'heap>, - mut expr: Expr<'heap>, - ) -> Result { - let environment = Environment::new(heap); - let registry = ModuleRegistry::new(&environment); - - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); - - let mut expander = SpecialFormExpander::new(heap); - expander.visit_expr(&mut expr); - - process_issues(diagnostics, expander.take_diagnostics())?; - - let mut namespace = ModuleNamespace::new(®istry); - namespace.import_prelude(); - - let mut resolver = ImportResolver::new(heap, namespace); - resolver.visit_expr(&mut expr); - - process_issues(diagnostics, resolver.take_diagnostics())?; - - Ok(expr.syntax_dump_to_string()) - } -} diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver_continue.rs b/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver_continue.rs deleted file mode 100644 index d21cd1287bf..00000000000 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_import_resolver_continue.rs +++ /dev/null @@ -1,68 +0,0 @@ -use hashql_ast::{ - format::SyntaxDump as _, - lowering::{ - import_resolver::ImportResolver, pre_expansion_name_resolver::PreExpansionNameResolver, - special_form_expander::SpecialFormExpander, - }, - node::expr::Expr, - visit::Visitor as _, -}; -use hashql_core::{ - module::{ModuleRegistry, namespace::ModuleNamespace}, - r#type::environment::Environment, -}; -use hashql_diagnostics::Diagnostic; - -use super::{RunContext, Suite, SuiteDiagnostic}; - -pub(crate) struct AstLoweringImportResolverContinueSuite; - -impl Suite for AstLoweringImportResolverContinueSuite { - fn name(&self) -> &'static str { - "ast/lowering/import-resolver/continue" - } - - fn description(&self) -> &'static str { - "Import resolution with non-fatal error handling" - } - - fn run<'heap>( - &self, - RunContext { - heap, diagnostics, .. - }: RunContext<'_, 'heap>, - mut expr: Expr<'heap>, - ) -> Result { - let environment = Environment::new(heap); - let registry = ModuleRegistry::new(&environment); - - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); - - let mut expander = SpecialFormExpander::new(heap); - expander.visit_expr(&mut expr); - - diagnostics.extend( - expander - .take_diagnostics() - .into_iter() - .map(Diagnostic::boxed), - ); - - let mut namespace = ModuleNamespace::new(®istry); - namespace.import_prelude(); - - let mut resolver = ImportResolver::new(heap, namespace); - resolver.visit_expr(&mut expr); - - diagnostics.extend( - resolver - .take_diagnostics() - .into_iter() - .map(Diagnostic::boxed), - ); - - Ok(expr.syntax_dump_to_string()) - } -} diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_pre_expansion_name_resolver.rs b/libs/@local/hashql/compiletest/src/suite/ast_lowering_pre_expansion_name_resolver.rs deleted file mode 100644 index 8116282aee4..00000000000 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_pre_expansion_name_resolver.rs +++ /dev/null @@ -1,34 +0,0 @@ -use hashql_ast::{ - format::SyntaxDump as _, lowering::pre_expansion_name_resolver::PreExpansionNameResolver, - node::expr::Expr, visit::Visitor as _, -}; -use hashql_core::{module::ModuleRegistry, r#type::environment::Environment}; - -use super::{RunContext, Suite, SuiteDiagnostic}; - -pub(crate) struct AstLoweringNameResolverSuite; - -impl Suite for AstLoweringNameResolverSuite { - fn name(&self) -> &'static str { - "ast/lowering/pre-expansion-name-resolver" - } - - fn description(&self) -> &'static str { - "Name resolution before macro expansion" - } - - fn run<'heap>( - &self, - RunContext { heap, .. }: RunContext<'_, 'heap>, - mut expr: Expr<'heap>, - ) -> Result { - let environment = Environment::new(heap); - let registry = ModuleRegistry::new(&environment); - - let mut resolver = PreExpansionNameResolver::new(®istry); - - resolver.visit_expr(&mut expr); - - Ok(expr.syntax_dump_to_string()) - } -} diff --git a/libs/@local/hashql/compiletest/src/suite/ast_lowering_special_form_expander.rs b/libs/@local/hashql/compiletest/src/suite/ast_lowering_special_form_expander.rs deleted file mode 100644 index c7dceb6f5f6..00000000000 --- a/libs/@local/hashql/compiletest/src/suite/ast_lowering_special_form_expander.rs +++ /dev/null @@ -1,34 +0,0 @@ -use hashql_ast::{ - format::SyntaxDump as _, lowering::special_form_expander::SpecialFormExpander, - node::expr::Expr, visit::Visitor as _, -}; - -use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues}; - -pub(crate) struct AstLoweringSpecialFormExpanderSuite; - -impl Suite for AstLoweringSpecialFormExpanderSuite { - fn name(&self) -> &'static str { - "ast/lowering/special-form-expander" - } - - fn description(&self) -> &'static str { - "Special form and macro expansion in the AST" - } - - fn run<'heap>( - &self, - RunContext { - heap, diagnostics, .. - }: RunContext<'_, 'heap>, - mut expr: Expr<'heap>, - ) -> Result { - let mut expander = SpecialFormExpander::new(heap); - - expander.visit_expr(&mut expr); - - process_issues(diagnostics, expander.take_diagnostics())?; - - Ok(expr.syntax_dump_to_string()) - } -} diff --git a/libs/@local/hashql/compiletest/src/suite/hir_lower_alias_replacement.rs b/libs/@local/hashql/compiletest/src/suite/hir_lower_alias_replacement.rs index bb6a145fcf1..5059bf98f67 100644 --- a/libs/@local/hashql/compiletest/src/suite/hir_lower_alias_replacement.rs +++ b/libs/@local/hashql/compiletest/src/suite/hir_lower_alias_replacement.rs @@ -1,6 +1,6 @@ use core::fmt::Write as _; -use hashql_ast::{lowering::ExtractedTypes, node::expr::Expr}; +use hashql_ast::{lower::ExtractedTypes, node::expr::Expr}; use hashql_core::{ heap::Heap, module::ModuleRegistry, diff --git a/libs/@local/hashql/compiletest/src/suite/hir_reify.rs b/libs/@local/hashql/compiletest/src/suite/hir_reify.rs index bb27d5d5c16..2e26a08b40b 100644 --- a/libs/@local/hashql/compiletest/src/suite/hir_reify.rs +++ b/libs/@local/hashql/compiletest/src/suite/hir_reify.rs @@ -1,9 +1,9 @@ use hashql_ast::{ - lowering::{ExtractedTypes, lower}, + lower::{ExtractedTypes, lower}, node::expr::Expr, }; use hashql_core::{ - heap::Heap, + heap::{Heap, Scratch}, module::ModuleRegistry, pretty::{Formatter, RenderOptions}, r#type::environment::Environment, @@ -24,11 +24,13 @@ pub(crate) fn hir_reify<'heap>( context: &mut HirContext<'_, 'heap>, diagnostics: &mut Vec, ) -> Result<(Node<'heap>, ExtractedTypes<'heap>), SuiteDiagnostic> { + let mut scratch = Scratch::new(); let result = lower( heap.intern_symbol("::main"), &mut expr, environment, context.modules, + &mut scratch, ); let types = process_status(diagnostics, result)?; diff --git a/libs/@local/hashql/compiletest/src/suite/mir_reify.rs b/libs/@local/hashql/compiletest/src/suite/mir_reify.rs index 6e813cae28a..38addae107f 100644 --- a/libs/@local/hashql/compiletest/src/suite/mir_reify.rs +++ b/libs/@local/hashql/compiletest/src/suite/mir_reify.rs @@ -38,12 +38,14 @@ pub(crate) fn mir_reify<'heap>( let hir_interner = hashql_hir::intern::Interner::new(heap); let mut hir_context = HirContext::new(&hir_interner, ®istry); - let result = hashql_ast::lowering::lower( + let result = hashql_ast::lower::lower( heap.intern_symbol("::main"), &mut expr, environment, hir_context.modules, + &mut scratch, ); + scratch.reset(); let types = process_status(diagnostics, result)?; let node = process_status( diff --git a/libs/@local/hashql/compiletest/src/suite/mod.rs b/libs/@local/hashql/compiletest/src/suite/mod.rs index 69edab286f1..89f3f62165f 100644 --- a/libs/@local/hashql/compiletest/src/suite/mod.rs +++ b/libs/@local/hashql/compiletest/src/suite/mod.rs @@ -1,13 +1,10 @@ #![coverage(off)] -mod ast_lowering_import_resolver; -mod ast_lowering_import_resolver_continue; -mod ast_lowering_node_mangler; -mod ast_lowering_node_renumberer; -mod ast_lowering_pre_expansion_name_resolver; -mod ast_lowering_sanitizer; -mod ast_lowering_special_form_expander; -mod ast_lowering_type_definition_extractor; -mod ast_lowering_type_extractor; +mod ast_lower_expander; +mod ast_lower_node_mangler; +mod ast_lower_node_renumberer; +mod ast_lower_sanitizer; +mod ast_lower_type_definition_extractor; +mod ast_lower_type_extractor; pub(crate) mod common; mod eval_postgres; mod hir_lower_alias_replacement; @@ -41,15 +38,11 @@ use hashql_core::{collections::FastHashMap, heap::Heap, span::SpanId}; use hashql_diagnostics::{Diagnostic, category::DiagnosticCategory}; use self::{ - ast_lowering_import_resolver::AstLoweringImportResolverSuite, - ast_lowering_import_resolver_continue::AstLoweringImportResolverContinueSuite, - ast_lowering_node_mangler::AstLoweringNameManglerSuite, - ast_lowering_node_renumberer::AstLoweringNodeRenumbererSuite, - ast_lowering_pre_expansion_name_resolver::AstLoweringNameResolverSuite, - ast_lowering_sanitizer::AstLoweringSanitizerSuite, - ast_lowering_special_form_expander::AstLoweringSpecialFormExpanderSuite, - ast_lowering_type_definition_extractor::AstLoweringTypeDefinitionExtractorSuite, - ast_lowering_type_extractor::AstLoweringTypeExtractorSuite, eval_postgres::EvalPostgres, + ast_lower_expander::AstLowerExpanderSuite, ast_lower_node_mangler::AstLowerNameManglerSuite, + ast_lower_node_renumberer::AstLowerNodeRenumbererSuite, + ast_lower_sanitizer::AstLowerSanitizerSuite, + ast_lower_type_definition_extractor::AstLowerTypeDefinitionExtractorSuite, + ast_lower_type_extractor::AstLowerTypeExtractorSuite, eval_postgres::EvalPostgres, hir_lower_alias_replacement::HirLowerAliasReplacementSuite, hir_lower_checking::HirLowerTypeCheckingSuite, hir_lower_ctor::HirLowerCtorSuite, hir_lower_graph_hoisting::HirLowerGraphHoistingSuite, @@ -141,15 +134,12 @@ pub(crate) trait Suite: RefUnwindSafe + Send + Sync + 'static { } const SUITES: &[&dyn Suite] = &[ - &AstLoweringImportResolverContinueSuite, - &AstLoweringImportResolverSuite, - &AstLoweringNameManglerSuite, - &AstLoweringNameResolverSuite, - &AstLoweringNodeRenumbererSuite, - &AstLoweringSanitizerSuite, - &AstLoweringSpecialFormExpanderSuite, - &AstLoweringTypeDefinitionExtractorSuite, - &AstLoweringTypeExtractorSuite, + &AstLowerExpanderSuite, + &AstLowerNameManglerSuite, + &AstLowerNodeRenumbererSuite, + &AstLowerSanitizerSuite, + &AstLowerTypeDefinitionExtractorSuite, + &AstLowerTypeExtractorSuite, &EvalPostgres, &HirLowerAliasReplacementSuite, &HirLowerCtorSuite, diff --git a/libs/@local/hashql/core/src/collections/mod.rs b/libs/@local/hashql/core/src/collections/mod.rs index 930b4ba50d2..5f7c9c4bf4a 100644 --- a/libs/@local/hashql/core/src/collections/mod.rs +++ b/libs/@local/hashql/core/src/collections/mod.rs @@ -14,6 +14,8 @@ pub type FastHasher = foldhash::fast::RandomState; pub type FastHashMap = HashMap; pub type FastHashMapEntry<'map, K, V, A = Global> = hashbrown::hash_map::Entry<'map, K, V, FastHasher, A>; +pub type FastHashMapIntoIter = hashbrown::hash_map::IntoIter; +pub type FastHashMapIter<'map, K, V> = hashbrown::hash_map::Iter<'map, K, V>; pub type FastHashSet = HashSet; pub type FastHashSetEntry<'map, T, A = Global> = hashbrown::hash_set::Entry<'map, T, FastHasher, A>; diff --git a/libs/@local/hashql/core/src/module/error.rs b/libs/@local/hashql/core/src/module/error.rs index 07a6b769d62..65460709c7e 100644 --- a/libs/@local/hashql/core/src/module/error.rs +++ b/libs/@local/hashql/core/src/module/error.rs @@ -1,6 +1,129 @@ -use super::{ModuleId, Universe, import::Import, item::Item, resolver::Reference}; +use core::fmt; + +use super::{ + ModuleId, Universe, + import::Import, + item::{Item, ItemKind}, + resolver::Reference, +}; use crate::symbol::Symbol; +/// A set of item kinds that a name was found to exist as. +/// +/// Used in resolution errors to report what a name actually resolves to +/// when the expected kind was not found. For example, when looking for a +/// value named `Url` that only exists as a type, the error can report +/// `expected: Value, found: KindSet::TYPE`. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)] +pub struct KindSet(u8); + +impl KindSet { + pub const EMPTY: Self = Self(0); + pub const MODULE: Self = Self(0b100); + pub const TYPE: Self = Self(0b010); + pub const VALUE: Self = Self(0b001); + + #[must_use] + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 && other.0 != 0 + } + + #[must_use] + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + #[must_use] + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// Removes the kind corresponding to `universe` from this set. + #[must_use] + pub const fn without_universe(self, universe: Universe) -> Self { + Self(self.0 & !Self::from_universe(universe).0) + } + + /// Returns the `KindSet` for a single universe. + #[must_use] + pub const fn from_universe(universe: Universe) -> Self { + match universe { + Universe::Value => Self::VALUE, + Universe::Type => Self::TYPE, + } + } + + /// Derives a `KindSet` from an [`ItemKind`]. + #[must_use] + pub const fn from_item_kind(kind: &ItemKind<'_>) -> Self { + match kind.universe() { + None => Self::MODULE, + Some(universe) => Self::from_universe(universe), + } + } +} + +impl fmt::Debug for KindSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut first = true; + f.write_str("KindSet(")?; + for (flag, name) in [ + (Self::VALUE, "VALUE"), + (Self::TYPE, "TYPE"), + (Self::MODULE, "MODULE"), + ] { + if self.contains(flag) { + if !first { + f.write_str(" | ")?; + } + f.write_str(name)?; + first = false; + } + } + if first { + f.write_str("EMPTY")?; + } + f.write_str(")") + } +} + +impl fmt::Display for KindSet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let has_value = self.contains(Self::VALUE); + let has_type = self.contains(Self::TYPE); + let has_module = self.contains(Self::MODULE); + + let mut parts = Vec::new(); + if has_value { + parts.push("a value"); + } + if has_type { + parts.push("a type"); + } + if has_module { + parts.push("a module"); + } + + match parts.len() { + 0 => Ok(()), + 1 => f.write_str(parts[0]), + 2 => write!(f, "{} and {}", parts[0], parts[1]), + _ => { + for (index, part) in parts.iter().enumerate() { + if index > 0 { + f.write_str(", ")?; + } + if index == parts.len() - 1 { + f.write_str("and ")?; + } + f.write_str(part)?; + } + Ok(()) + } + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ResolutionSuggestion<'heap, T> { pub item: T, @@ -25,6 +148,12 @@ pub enum ResolutionError<'heap> { ImportNotFound { depth: usize, name: Symbol<'heap>, + /// The universe that was searched, if resolution was single-universe. + /// `None` when all universes were searched (multi-mode). + expected: Option, + /// What kinds the name actually exists as (when it was not found in + /// the expected universe). + found: KindSet, suggestions: Vec>>, }, @@ -37,6 +166,13 @@ pub enum ResolutionError<'heap> { ItemNotFound { depth: usize, name: Symbol<'heap>, + /// The universe that was searched, if resolution was single-universe. + /// `None` when all universes were searched (multi-mode). + expected: Option, + /// What kinds the name actually exists as (when it was not found in + /// the expected universe). For example, looking for value `Url` in a + /// module that only exports it as a type yields `KindSet::TYPE`. + found: KindSet, suggestions: Vec>>, }, diff --git a/libs/@local/hashql/core/src/module/item.rs b/libs/@local/hashql/core/src/module/item.rs index 58402c923dc..bb4906df8e3 100644 --- a/libs/@local/hashql/core/src/module/item.rs +++ b/libs/@local/hashql/core/src/module/item.rs @@ -5,19 +5,19 @@ use crate::symbol::Symbol; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct IntrinsicValueItem<'heap> { - pub name: &'static str, + pub name: Symbol<'heap>, pub r#type: TypeDef<'heap>, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct IntrinsicTypeItem { - pub name: &'static str, +pub struct IntrinsicTypeItem<'heap> { + pub name: Symbol<'heap>, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, derive_more::From)] pub enum IntrinsicItem<'heap> { Value(IntrinsicValueItem<'heap>), - Type(IntrinsicTypeItem), + Type(IntrinsicTypeItem<'heap>), } impl IntrinsicItem<'_> { @@ -94,10 +94,11 @@ impl<'heap> Item<'heap> { }) } + // TODO: deprecate pub fn absolute_path( &self, registry: &ModuleRegistry<'heap>, - ) -> impl Iterator> { + ) -> impl IntoIterator> { let mut modules: Vec<_> = self.ancestors(registry).into_iter().collect(); modules.reverse(); @@ -106,4 +107,45 @@ impl<'heap> Item<'heap> { .map(|module| module.name) .chain(iter::once(self.name)) } + + pub fn absolute_path_rev( + &self, + registry: &ModuleRegistry<'heap>, + ) -> impl ExactSizeIterator> { + struct Iter { + inner: I, + remaining: u32, + } + + impl Iterator for Iter + where + I: Iterator, + { + type Item = I::Item; + + fn next(&mut self) -> Option { + let item = self.inner.next()?; + self.remaining -= 1; + + Some(item) + } + + fn size_hint(&self) -> (usize, Option) { + (self.remaining as usize, Some(self.remaining as usize)) + } + } + + impl ExactSizeIterator for Iter where I: Iterator {} + + let depth = registry.module_depth(self.module); + + Iter { + inner: iter::once(self.name).chain( + self.ancestors(registry) + .into_iter() + .map(|module| module.name), + ), + remaining: depth + 1, + } + } } diff --git a/libs/@local/hashql/core/src/module/mod.rs b/libs/@local/hashql/core/src/module/mod.rs index d0b7e253255..ad56ed14a94 100644 --- a/libs/@local/hashql/core/src/module/mod.rs +++ b/libs/@local/hashql/core/src/module/mod.rs @@ -12,7 +12,7 @@ mod resolver; pub mod std_lib; pub mod universe; -use core::slice; +use core::{num::NonZero, slice}; use std::sync::RwLock; use self::{ @@ -98,11 +98,12 @@ impl<'heap> ModuleRegistry<'heap> { assert_eq!(item.module, id.value()); // check for modules if the parent is also set *correctly* to our module - if let ItemKind::Module(module) = item.kind { - let module = self.modules.index(module); + if let ItemKind::Module(child) = item.kind { + let child = self.modules.index(child); - assert_eq!(module.parent, id.value()); - assert_eq!(module.name, item.name); + assert_eq!(child.parent, id.value()); + assert_eq!(child.depth.get(), module.depth.get() + 1); + assert_eq!(child.name, item.name); } } } @@ -139,7 +140,7 @@ impl<'heap> ModuleRegistry<'heap> { /// # Panics /// /// This function will panic if the internal `RwLock` is poisoned. - fn find_by_name(&self, name: Symbol<'heap>) -> Option> { + pub fn find_by_name(&self, name: Symbol<'heap>) -> Option> { let root = self.root.read().expect("lock should not be poisoned"); let id = root.get(&name).copied()?; @@ -336,6 +337,16 @@ impl<'heap> ModuleRegistry<'heap> { None }) } + + pub fn module_depth(&self, id: ModuleId) -> u32 { + if id == ModuleId::ROOT { + return 0; + } + + let module = self.modules.index(id); + + module.depth.get() + } } /// A module in the HashQL language. @@ -349,6 +360,7 @@ pub struct Module<'heap> { pub name: Symbol<'heap>, pub parent: ModuleId, + pub depth: NonZero, pub items: Interned<'heap, [Item<'heap>]>, } @@ -379,6 +391,7 @@ impl<'heap> Module<'heap> { pub struct PartialModule<'heap> { name: Symbol<'heap>, parent: ModuleId, + depth: NonZero, items: Interned<'heap, [Item<'heap>]>, } @@ -390,6 +403,7 @@ impl<'heap> Decompose<'heap> for Module<'heap> { id, name: partial.name, parent: partial.parent, + depth: partial.depth, items: partial.items, } } diff --git a/libs/@local/hashql/core/src/module/namespace.rs b/libs/@local/hashql/core/src/module/namespace.rs index 607c489b555..072e0a91a35 100644 --- a/libs/@local/hashql/core/src/module/namespace.rs +++ b/libs/@local/hashql/core/src/module/namespace.rs @@ -174,6 +174,26 @@ impl<'env, 'heap> ModuleNamespace<'env, 'heap> { }); } + /// Bind a name to an existing registry item in this namespace. + /// + /// Unlike [`local`](Self::local), which creates an opaque binding that + /// resolves to [`Reference::Binding`], an alias resolves to + /// [`Reference::Item`], preserving the item's identity. This means + /// further rebindings of the name still resolve to the original item. + /// + /// For example, after `use ::core::math::add in`, writing + /// `let foo = add` makes `foo` resolve to `::core::math::add` because + /// the alias preserves the item through the `let` binding. + /// + /// Like all namespace mutations, an alias can be undone by rolling back + /// to a [`Snapshot`] taken before the call. + pub fn alias(&mut self, name: Symbol<'heap>, item: Item<'heap>) { + self.imports.push(Import { + name, + item: ImportReference::Item(item), + }); + } + /// Return all the locals that have been registered so far. #[must_use] pub fn locals(&self, universe: Universe) -> FastHashSet> { @@ -356,6 +376,11 @@ impl<'env, 'heap> ModuleNamespace<'env, 'heap> { successful &= self.import_absolute_static("List", ["kernel", "type", "List"]); successful &= self.import_absolute_static("Dict", ["kernel", "type", "Dict"]); + successful &= self.import_absolute_static("Union", ["kernel", "type", "Union"]); + successful &= self.import_absolute_static("|", ["kernel", "type", "Union"]); + successful &= + self.import_absolute_static("Intersection", ["kernel", "type", "Intersection"]); + successful &= self.import_absolute_static("&", ["kernel", "type", "Intersection"]); successful &= self.import_absolute_static("Null", ["kernel", "type", "Null"]); @@ -455,7 +480,7 @@ impl<'env, 'heap> ModuleNamespace<'env, 'heap> { #[cfg(test)] mod tests { #![coverage(off)] - use core::assert_matches; + use core::{assert_matches, num::NonZero}; use super::ModuleNamespace; use crate::{ @@ -495,9 +520,9 @@ mod tests { assert_matches!( item.kind, ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { - name: "::core::math::add", + name, r#type: _ - })) + })) if name.as_str() == "::core::math::add" ); } @@ -524,11 +549,11 @@ mod tests { assert_eq!(item.name.as_str(), "Dict"); assert_eq!(item.kind.universe(), Some(Universe::Type)); - assert_eq!( + assert_matches!( item.kind, ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { - name: "::kernel::type::Dict", - })) + name, + })) if name.as_str() == "::kernel::type::Dict" ); } @@ -562,9 +587,9 @@ mod tests { assert_matches!( item.kind, ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { - name: "::kernel::special_form::let", + name, r#type: _ - })) + })) if name.as_str() == "::kernel::special_form::let" ); } @@ -595,11 +620,11 @@ mod tests { assert_eq!(item.name.as_str(), "Dict"); assert_eq!(item.kind.universe(), Some(Universe::Type)); - assert_eq!( + assert_matches!( item.kind, ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { - name: "::kernel::type::Dict", - })) + name, + })) if name.as_str() == "::kernel::type::Dict" ); } @@ -646,10 +671,11 @@ mod tests { assert_matches!( item.kind, - ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { - name: "::kernel::special_form::let", + ItemKind::Intrinsic( + IntrinsicItem::Value(IntrinsicValueItem { + name, r#type: _ - })) + })) if name.as_str() == "::kernel::special_form::let" ); } @@ -664,13 +690,14 @@ mod tests { let module = registry.intern_module(|id| PartialModule { parent: ModuleId::ROOT, + depth: const { NonZero::new(1).unwrap() }, name: heap.intern_symbol("foo"), items: registry.intern_items(&[Item { module: id.value(), name: heap.intern_symbol("bar"), kind: ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { - name: "::foo::bar", + name: heap.intern_symbol("::foo::bar"), })), }]), }); @@ -690,11 +717,11 @@ mod tests { assert_eq!(import.name.as_str(), "Dict"); assert_eq!(import.kind.universe(), Some(Universe::Type)); - assert_eq!( + assert_matches!( import.kind, ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { - name: "::kernel::type::Dict", - })) + name, + })) if name.as_str() == "::kernel::type::Dict" ); namespace @@ -723,11 +750,11 @@ mod tests { assert_eq!(import.name.as_str(), "bar"); assert_eq!(import.kind.universe(), Some(Universe::Type)); - assert_eq!( + assert_matches!( import.kind, ItemKind::Intrinsic(IntrinsicItem::Type(IntrinsicTypeItem { - name: "::foo::bar" - })) + name, + })) if name.as_str() == "::foo::bar" ); } @@ -921,4 +948,170 @@ mod tests { assert_matches!(item, Reference::Binding(_)); } + + #[test] + fn alias_shadows_import() { + let heap = Heap::new(); + let environment = Environment::new(&heap); + let registry = ModuleRegistry::new(&environment); + + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); + + // `+` resolves to `::core::math::add` via the prelude + let original = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("prelude should have `+`") + .expect_item(); + + assert_eq!(original.name.as_str(), "add"); + + // Create a custom item and alias `+` to it + let ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { + r#type: original_type, + .. + })) = original.kind + else { + panic!("expected intrinsic value item"); + }; + + let custom_module = registry.intern_module(|id| PartialModule { + parent: ModuleId::ROOT, + depth: const { NonZero::new(1).unwrap() }, + name: heap.intern_symbol("custom"), + + items: registry.intern_items(&[Item { + module: id.value(), + name: heap.intern_symbol("my_add"), + kind: ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { + name: heap.intern_symbol("::custom::my_add"), + r#type: original_type, + })), + }]), + }); + registry.register(custom_module); + + let custom_item = *registry + .modules + .index(custom_module) + .items + .into_iter() + .next() + .expect("module should have one item"); + + namespace.alias(heap.intern_symbol("+"), custom_item); + + // `+` now resolves to the aliased item, not the prelude import + let aliased = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("alias should be resolvable") + .expect_item(); + + assert_eq!(aliased.name.as_str(), "my_add"); + assert_matches!( + aliased.kind, + ItemKind::Intrinsic(IntrinsicItem::Value(IntrinsicValueItem { + name, + r#type: _ + })) if name.as_str() == "::custom::my_add" + ); + } + + #[test] + fn alias_rollback() { + let heap = Heap::new(); + let environment = Environment::new(&heap); + let registry = ModuleRegistry::new(&environment); + + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); + + let original = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("prelude should have `+`") + .expect_item(); + + let snapshot = namespace.snapshot(); + + // Alias `+` to itself under a different name (simulating a `use` rebinding) + namespace.alias(heap.intern_symbol("+"), original); + + // Still resolves (to the alias, which points to the same item) + let during = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("alias should be resolvable") + .expect_item(); + + assert_eq!(during.name.as_str(), "add"); + + namespace.rollback_to(snapshot); + + // After rollback, `+` resolves through the original prelude import + let after = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("prelude import should still work after rollback") + .expect_item(); + + assert_eq!(after.name.as_str(), "add"); + } + + #[test] + fn alias_not_in_locals() { + let heap = Heap::new(); + let environment = Environment::new(&heap); + let registry = ModuleRegistry::new(&environment); + + let mut namespace = ModuleNamespace::new(®istry); + namespace.import_prelude(); + + let item = namespace + .resolve_relative( + [heap.intern_symbol("+")], + ResolveOptions { + universe: Universe::Value, + mode: ResolutionMode::Relative, + }, + ) + .expect("prelude should have `+`") + .expect_item(); + + namespace.alias(heap.intern_symbol("my_add"), item); + + // An alias is not a local binding, so it should not appear in locals + assert!( + !namespace + .locals(Universe::Value) + .contains(&heap.intern_symbol("my_add")) + ); + } } diff --git a/libs/@local/hashql/core/src/module/resolver.rs b/libs/@local/hashql/core/src/module/resolver.rs index c2edc6827b1..1dd95181553 100644 --- a/libs/@local/hashql/core/src/module/resolver.rs +++ b/libs/@local/hashql/core/src/module/resolver.rs @@ -2,7 +2,7 @@ use core::{fmt::Debug, iter}; use super::{ Module, ModuleId, ModuleRegistry, Universe, - error::{ResolutionError, ResolutionSuggestion}, + error::{KindSet, ResolutionError, ResolutionSuggestion}, import::Import, item::{Item, ItemKind}, locals::LocalBinding, @@ -97,6 +97,14 @@ impl<'heap> Resolver<'_, 'heap> { } } + fn found_kinds(&self, call: impl FnOnce() -> KindSet) -> KindSet { + if self.options.suggestions { + call() + } else { + KindSet::EMPTY + } + } + fn resolve_single( &self, module: Module<'heap>, @@ -111,9 +119,22 @@ impl<'heap> Resolver<'_, 'heap> { .find(|item| item.name == name && item.kind.universe() == Some(universe)); let Some(item) = item else { + // Check what kinds the name actually exists as in this module. + let found = self.found_kinds(|| { + let mut found = KindSet::EMPTY; + for candidate in module.items { + if candidate.name == name { + found = found.union(KindSet::from_item_kind(&candidate.kind)); + } + } + found + }); + return Err(ResolutionError::ItemNotFound { depth, name, + expected: Some(universe), + found, suggestions: self .suggest(|| module.suggestions(|item| item.kind.universe() == Some(universe))), }); @@ -141,6 +162,8 @@ impl<'heap> Resolver<'_, 'heap> { return Err(ResolutionError::ItemNotFound { depth, name, + expected: None, + found: KindSet::EMPTY, suggestions: self.suggest(|| module.suggestions(|_| true)), }); } @@ -287,9 +310,29 @@ impl<'heap> Resolver<'_, 'heap> { .find(|import| import.name == name && import.item.universe() == Some(universe)); let Some(import) = import else { + // Check what kinds the name actually exists as in the imports. + let found = self.found_kinds(|| { + let mut found = KindSet::EMPTY; + for candidate in imports.iter().rev() { + if candidate.name == name { + match candidate.item.universe() { + Some(universe) => { + found = found.union(KindSet::from_universe(universe)); + } + None => { + found = found.union(KindSet::MODULE); + } + } + } + } + found + }); + return Err(ResolutionError::ImportNotFound { depth: 0, name, + expected: Some(universe), + found, suggestions: self.suggest(|| { imports .iter() @@ -342,6 +385,8 @@ impl<'heap> Resolver<'_, 'heap> { return Err(ResolutionError::ImportNotFound { depth: 0, name, + expected: None, + found: KindSet::EMPTY, suggestions: self.suggest(|| { imports .iter() @@ -426,6 +471,18 @@ impl<'heap> Resolver<'_, 'heap> { let module = Self::find_module_from_imports(name, imports); let Some(module) = module else { + // Check if the name exists as a non-module import. If so, + // produce ModuleRequired ("X is a value, not a module") + // instead of ModuleNotFound ("cannot find module X"). + let non_module = imports.iter().rev().find(|import| import.name == name); + + if let Some(non_module) = non_module { + return Err(ResolutionError::ModuleRequired { + depth: 0, + found: non_module.item.universe(), + }); + } + return Err(ResolutionError::ModuleNotFound { depth: 0, name, @@ -469,7 +526,7 @@ impl<'heap> Resolver<'_, 'heap> { #[cfg(test)] mod test { #![coverage(off)] - use core::assert_matches; + use core::{assert_matches, num::NonZero}; use super::{Reference, ResolutionError}; use crate::{ @@ -575,7 +632,7 @@ mod test { ]) .expect_err("Resolution should fail for non-existent item"); - assert_matches!(error, ResolutionError::ItemNotFound { depth: 2, name: _, suggestions } if suggestions.is_empty()); + assert_matches!(error, ResolutionError::ItemNotFound { depth: 2, name: _, expected: Some(Universe::Type), found: _, suggestions } if suggestions.is_empty()); } #[test] @@ -839,6 +896,7 @@ mod test { PartialModule { name: heap.intern_symbol("empty_module"), parent: ModuleId::ROOT, + depth: const { NonZero::new(1).unwrap() }, items: registry.intern_items(&[]), // No items } }); diff --git a/libs/@local/hashql/core/src/module/std_lib/core/bits.rs b/libs/@local/hashql/core/src/module/std_lib/core/bits.rs index 0a238d6475b..4e8d7028f00 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/bits.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/bits.rs @@ -1,11 +1,10 @@ use super::func; use crate::{ - heap::Heap, module::{ locals::TypeDef, std_lib::{ModuleDef, StandardLibrary, StandardLibraryModule, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Bits { @@ -15,8 +14,8 @@ pub(in crate::module::std_lib) struct Bits { impl<'heap> StandardLibraryModule<'heap> for Bits { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("bits") + fn name() -> Symbol<'heap> { + sym::bits } #[expect(non_snake_case)] @@ -27,41 +26,41 @@ impl<'heap> StandardLibraryModule<'heap> for Bits { let items = [ ( - "::core::bits::and", - &["&"], + sym::path::core::bits::and, + &[sym::and, sym::symbol::ampersand], decl!(lib; <>(lhs: Integer, rhs: Integer) -> Integer), ), ( - "::core::bits::or", - &["|"], + sym::path::core::bits::or, + &[sym::or, sym::symbol::pipe], decl!(lib; <>(lhs: Integer, rhs: Integer) -> Integer), ), ( - "::core::bits::xor", - &["^"], + sym::path::core::bits::xor, + &[sym::xor, sym::symbol::caret], decl!(lib; <>(lhs: Integer, rhs: Integer) -> Integer), ), ( - "::core::bits::not", - &["~"], + sym::path::core::bits::not, + &[sym::not, sym::symbol::tilde], decl!(lib; <>(value: Integer) -> Integer), ), ( - "::core::bits::shl", - &["<<"], + sym::path::core::bits::shl, + &[sym::shl, sym::symbol::ltlt], // In the future we might want to specialize the `shift` to `Natural` decl!(lib; <>(value: Integer, shift: Integer) -> Integer), ), ( - "::core::bits::shr", - &[">>"], + sym::path::core::bits::shr, + &[sym::shr, sym::symbol::gtgt], // In the future we might want to specialize the `shift` to `Natural` decl!(lib; <>(value: Integer, shift: Integer) -> Integer), ), ]; for (name, alias, r#type) in items { - func(lib, &mut def, name, alias, r#type); + func(&mut def, name, alias.iter().copied(), r#type); } def diff --git a/libs/@local/hashql/core/src/module/std_lib/core/bool.rs b/libs/@local/hashql/core/src/module/std_lib/core/bool.rs index 3bc8ce99f0f..063e24345d7 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/bool.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/bool.rs @@ -1,11 +1,10 @@ use super::func; use crate::{ - heap::Heap, module::{ locals::TypeDef, std_lib::{ModuleDef, StandardLibrary, StandardLibraryModule, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Bool { @@ -15,8 +14,8 @@ pub(in crate::module::std_lib) struct Bool { impl<'heap> StandardLibraryModule<'heap> for Bool { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("bool") + fn name() -> Symbol<'heap> { + sym::bool } #[expect(non_snake_case)] @@ -27,24 +26,24 @@ impl<'heap> StandardLibraryModule<'heap> for Bool { let items = [ ( - "::core::bool::not", - &["!"], + sym::path::core::bool::not, + &[sym::not, sym::symbol::exclamation], decl!(lib; <>(value: Boolean) -> Boolean), ), ( - "::core::bool::and", - &["&&"], + sym::path::core::bool::and, + &[sym::and, sym::symbol::ampamp], decl!(lib; <>(lhs: Boolean, rhs: Boolean) -> Boolean), ), ( - "::core::bool::or", - &["||"], + sym::path::core::bool::or, + &[sym::or, sym::symbol::pipepipe], decl!(lib; <>(lhs: Boolean, rhs: Boolean) -> Boolean), ), ]; for (name, alias, r#type) in items { - func(lib, &mut def, name, alias, r#type); + func(&mut def, name, alias.iter().copied(), r#type); } def diff --git a/libs/@local/hashql/core/src/module/std_lib/core/cmp.rs b/libs/@local/hashql/core/src/module/std_lib/core/cmp.rs index 2490a3d096c..23a2d5ab736 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/cmp.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/cmp.rs @@ -1,11 +1,10 @@ use super::func; use crate::{ - heap::Heap, module::{ locals::TypeDef, std_lib::{ModuleDef, StandardLibrary, StandardLibraryModule, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Cmp { @@ -15,8 +14,8 @@ pub(in crate::module::std_lib) struct Cmp { impl<'heap> StandardLibraryModule<'heap> for Cmp { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("cmp") + fn name() -> Symbol<'heap> { + sym::cmp } #[expect(non_snake_case)] @@ -28,39 +27,39 @@ impl<'heap> StandardLibraryModule<'heap> for Cmp { let items = [ ( - "::core::cmp::gt", - &[">"], + sym::path::core::cmp::gt, + &[sym::gt, sym::symbol::gt], decl!(lib; <>(lhs: Number, rhs: Number) -> Boolean), ), ( - "::core::cmp::lt", - &["<"], + sym::path::core::cmp::lt, + &[sym::lt, sym::symbol::lt], decl!(lib; <>(lhs: Number, rhs: Number) -> Boolean), ), ( - "::core::cmp::gte", - &[">="], + sym::path::core::cmp::gte, + &[sym::gte, sym::symbol::gteq], decl!(lib; <>(lhs: Number, rhs: Number) -> Boolean), ), ( - "::core::cmp::lte", - &["<="], + sym::path::core::cmp::lte, + &[sym::lte, sym::symbol::lteq], decl!(lib; <>(lhs: Number, rhs: Number) -> Boolean), ), ( - "::core::cmp::eq", - &["=="], + sym::path::core::cmp::eq, + &[sym::eq, sym::symbol::eqeq], decl!(lib; (lhs: T, rhs: U) -> Boolean), ), ( - "::core::cmp::ne", - &["!="], + sym::path::core::cmp::ne, + &[sym::ne, sym::symbol::excleq], decl!(lib; (lhs: T, rhs: U) -> Boolean), ), ]; for (name, alias, r#type) in items { - func(lib, &mut def, name, alias, r#type); + func(&mut def, name, alias.iter().copied(), r#type); } def diff --git a/libs/@local/hashql/core/src/module/std_lib/core/json.rs b/libs/@local/hashql/core/src/module/std_lib/core/json.rs index 663e48ad490..17383585dcd 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/json.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/json.rs @@ -1,7 +1,6 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Json { @@ -11,8 +10,8 @@ pub(in crate::module::std_lib) struct Json { impl<'heap> StandardLibraryModule<'heap> for Json { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("json") + fn name() -> Symbol<'heap> { + sym::json } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/core/math.rs b/libs/@local/hashql/core/src/module/std_lib/core/math.rs index 68af7f7cf2e..2690ca0715b 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/math.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/math.rs @@ -1,11 +1,10 @@ use super::func; use crate::{ - heap::Heap, module::{ locals::TypeDef, std_lib::{ModuleDef, StandardLibrary, StandardLibraryModule, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Math { @@ -15,11 +14,11 @@ pub(in crate::module::std_lib) struct Math { impl<'heap> StandardLibraryModule<'heap> for Math { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("math") + fn name() -> Symbol<'heap> { + sym::math } - #[expect(clippy::non_ascii_literal, non_snake_case)] + #[expect(non_snake_case)] fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { let mut def = ModuleDef::new(); @@ -28,60 +27,64 @@ impl<'heap> StandardLibraryModule<'heap> for Math { let items = [ ( - "::core::math::add", - &["+"] as &[&'static str], + sym::path::core::math::add, + &[sym::add, sym::symbol::plus] as &[Symbol<'heap>], decl!(lib; (lhs: T, rhs: U) -> lib.ty.union([T, U])), ), ( - "::core::math::sub", - &["-"], + sym::path::core::math::sub, + &[sym::sub, sym::symbol::minus], decl!(lib; (lhs: T, rhs: U) -> lib.ty.union([T, U])), ), ( - "::core::math::mul", - &["*"], + sym::path::core::math::mul, + &[sym::mul, sym::symbol::asterisk], decl!(lib; (lhs: T, rhs: U) -> lib.ty.union([T, U])), ), ( - "::core::math::div", - &["/"], + sym::path::core::math::div, + &[sym::div, sym::symbol::slash], decl!(lib; <>(dividend: Number, divisor: Number) -> Number), ), ( - "::core::math::rem", - &["%"], + sym::path::core::math::rem, + &[sym::rem, sym::symbol::percent], decl!(lib; <>(dividend: Integer, divisor: Integer) -> Integer), ), ( - "::core::math::mod", - &[], + sym::path::core::math::r#mod, + &[sym::r#mod], decl!(lib; <>(value: Integer, modulus: Integer) -> Integer), ), ( - "::core::math::pow", - &["**", "↑"], + sym::path::core::math::pow, + &[ + sym::pow, + sym::symbol::asteriskasterisk, + sym::symbol::upwards, + ], // (cannot be `Integer` on return, as `exponent` can be a negative integer) decl!(lib; <>(base: Number, exponent: Number) -> Number), ), ( - "::core::math::sqrt", - &["√"], + sym::path::core::math::sqrt, + &[sym::sqrt, sym::symbol::sqrt], decl!(lib; <>(value: Number) -> Number), ), ( - "::core::math::cbrt", - &["∛"], + sym::path::core::math::cbrt, + &[sym::cbrt, sym::symbol::cbrt], decl!(lib; <>(value: Number) -> Number), ), ( - "::core::math::root", - &[], // cannot use `ⁿ√` because `ⁿ` is a letter, not a symbol + sym::path::core::math::root, + &[sym::root], // cannot use `ⁿ√` because `ⁿ` is a letter, not a symbol decl!(lib; <>(value: Number, root: Number) -> Number), ), ]; for (name, alias, r#type) in items { - func(lib, &mut def, name, alias, r#type); + func(&mut def, name, alias.iter().copied(), r#type); } def diff --git a/libs/@local/hashql/core/src/module/std_lib/core/mod.rs b/libs/@local/hashql/core/src/module/std_lib/core/mod.rs index fae43081c6b..968dcda1593 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/mod.rs @@ -1,10 +1,7 @@ -use core::iter; - use super::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}; use crate::{ - heap::Heap, module::{item::IntrinsicValueItem, locals::TypeDef}, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) mod bits; @@ -18,24 +15,16 @@ pub mod url; pub mod uuid; pub(in crate::module::std_lib) fn func<'heap>( - lib: &StandardLibrary<'_, 'heap>, def: &mut ModuleDef<'heap>, - name: &'static str, - alias: &[&'static str], + path: Symbol<'heap>, + names: impl IntoIterator>, r#type: TypeDef<'heap>, ) { - let value = IntrinsicValueItem { name, r#type }; - - let ident = name.rsplit_once("::").expect("path should be non-empty").1; + let value = IntrinsicValueItem { name: path, r#type }; - def.push_aliased( - iter::once(ident) - .chain(alias.iter().copied()) - .map(|name| lib.heap.intern_symbol(name)), - ItemDef::intrinsic(value), - ); + def.push_aliased(names, ItemDef::intrinsic(value)); } pub(in crate::module::std_lib) struct Core; @@ -53,8 +42,8 @@ impl<'heap> StandardLibraryModule<'heap> for Core { self::uuid::Uuid, ); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("core") + fn name() -> Symbol<'heap> { + sym::core } fn define(_: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/core/option.rs b/libs/@local/hashql/core/src/module/std_lib/core/option.rs index 7bf46204fa5..1075b8ae7e3 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/option.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/option.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, symbol::{Symbol, sym}, }; @@ -27,8 +26,8 @@ pub(in crate::module::std_lib) struct Option { impl<'heap> StandardLibraryModule<'heap> for Option { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("option") + fn name() -> Symbol<'heap> { + sym::option } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/core/result.rs b/libs/@local/hashql/core/src/module/std_lib/core/result.rs index 9f8a16dcf6e..91018546d28 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/result.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/result.rs @@ -1,7 +1,6 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Result { @@ -11,8 +10,8 @@ pub(in crate::module::std_lib) struct Result { impl<'heap> StandardLibraryModule<'heap> for Result { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("result") + fn name() -> Symbol<'heap> { + sym::result } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/core/url.rs b/libs/@local/hashql/core/src/module/std_lib/core/url.rs index 0335472f623..2cfb8ff0f8e 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/url.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/url.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, symbol::{Symbol, sym}, }; @@ -22,8 +21,8 @@ pub(in crate::module::std_lib) struct Url { impl<'heap> StandardLibraryModule<'heap> for Url { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("url") + fn name() -> Symbol<'heap> { + sym::url } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/core/uuid.rs b/libs/@local/hashql/core/src/module/std_lib/core/uuid.rs index a679193e885..de7ae31ca65 100644 --- a/libs/@local/hashql/core/src/module/std_lib/core/uuid.rs +++ b/libs/@local/hashql/core/src/module/std_lib/core/uuid.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, symbol::{Symbol, sym}, }; @@ -22,7 +21,7 @@ pub(in crate::module::std_lib) struct Uuid { impl<'heap> StandardLibraryModule<'heap> for Uuid { type Children = (); - fn name(_: &'heap Heap) -> Symbol<'heap> { + fn name() -> Symbol<'heap> { sym::uuid } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/body.rs b/libs/@local/hashql/core/src/module/std_lib/graph/body.rs index 2ce7f168822..d2db4de2838 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/body.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/body.rs @@ -1,11 +1,10 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, locals::TypeDef, std_lib::{self, ModuleDef, StandardLibraryModule, core::func, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Body { @@ -15,8 +14,8 @@ pub(in crate::module::std_lib) struct Body { impl<'heap> StandardLibraryModule<'heap> for Body { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("body") + fn name() -> Symbol<'heap> { + sym::body } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { @@ -40,7 +39,7 @@ impl<'heap> StandardLibraryModule<'heap> for Body { ) -> lib.ty.apply([(graph_returns.arguments[0].id, T)], graph_returns.id) ); - func(lib, &mut def, "::graph::body::filter", &[], decl); + func(&mut def, sym::path::graph_body_filter, [sym::filter], decl); def } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs index 8106254199a..5567dae0173 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, locals::TypeDef, @@ -9,7 +8,7 @@ use crate::{ decl, }, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Entity { @@ -22,8 +21,8 @@ pub(in crate::module::std_lib) struct Entity { impl<'heap> StandardLibraryModule<'heap> for Entity { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("entity") + fn name() -> Symbol<'heap> { + sym::entity } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { @@ -53,7 +52,12 @@ impl<'heap> StandardLibraryModule<'heap> for Entity { ) -> lib.ty.boolean() ); - func(lib, &mut def, "::graph::entity::is_of_type", &[], decl); + func( + &mut def, + sym::path::graph::entity::is_of_type, + [sym::is_of_type], + decl, + ); // `property(entity: Entity, path: JsonPath) -> Option` let decl = decl!(lib; @@ -62,7 +66,12 @@ impl<'heap> StandardLibraryModule<'heap> for Entity { ) -> option(&lib.ty, lib.ty.unknown()) ); - func(lib, &mut def, "::graph::entity::property", &[], decl); + func( + &mut def, + sym::path::graph::entity::property, + [sym::property], + decl, + ); def } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/head.rs b/libs/@local/hashql/core/src/module/std_lib/graph/head.rs index a157436cf54..5489040d803 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/head.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/head.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, locals::TypeDef, @@ -19,8 +18,8 @@ pub(in crate::module::std_lib) struct Head { impl<'heap> StandardLibraryModule<'heap> for Head { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("head") + fn name() -> Symbol<'heap> { + sym::head } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { @@ -51,10 +50,9 @@ impl<'heap> StandardLibraryModule<'heap> for Head { graph_ty.id, ); func( - lib, &mut def, - "::graph::head::entities", - &[], + sym::path::graph_head_entities, + [sym::entities], decl!(lib; <>(axis: query_temporal_axes_ty.id) -> entities_returns), ); diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/mod.rs index 7d6a3380645..ea6bc9ae423 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/mod.rs @@ -7,12 +7,11 @@ pub(in crate::module::std_lib) mod tmp; pub mod types; use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{ItemDef, ModuleDef, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Graph { @@ -30,8 +29,8 @@ impl<'heap> StandardLibraryModule<'heap> for Graph { self::types::Types, ); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("graph") + fn name() -> Symbol<'heap> { + sym::graph } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/tail.rs b/libs/@local/hashql/core/src/module/std_lib/graph/tail.rs index 91026a5dd1f..daf05eb5d9d 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/tail.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/tail.rs @@ -1,11 +1,10 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, locals::TypeDef, std_lib::{self, ModuleDef, StandardLibraryModule, core::func, decl}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Tail { @@ -15,8 +14,8 @@ pub(in crate::module::std_lib) struct Tail { impl<'heap> StandardLibraryModule<'heap> for Tail { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("tail") + fn name() -> Symbol<'heap> { + sym::tail } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { @@ -33,7 +32,12 @@ impl<'heap> StandardLibraryModule<'heap> for Tail { (graph: lib.ty.apply([(graph_ty.arguments[0].id, T)], graph_ty.id)) -> lib.ty.list(T) ); - func(lib, &mut def, "::graph::tail::collect", &[], decl); + func( + &mut def, + sym::path::graph_tail_collect, + [sym::collect], + decl, + ); def } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/temporal.rs b/libs/@local/hashql/core/src/module/std_lib/graph/temporal.rs index 26729210137..c5c1eb07f94 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/temporal.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/temporal.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, symbol::{Symbol, sym}, }; @@ -109,7 +108,7 @@ pub(in crate::module::std_lib) struct Temporal { impl<'heap> StandardLibraryModule<'heap> for Temporal { type Children = (); - fn name(_heap: &'heap Heap) -> Symbol<'heap> { + fn name() -> Symbol<'heap> { sym::temporal } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/tmp.rs b/libs/@local/hashql/core/src/module/std_lib/graph/tmp.rs index e9bfed600b5..e85867fb5b0 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/tmp.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/tmp.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, locals::TypeDef, @@ -16,8 +15,8 @@ pub(in crate::module::std_lib) struct Tmp { impl<'heap> StandardLibraryModule<'heap> for Tmp { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("tmp") + fn name() -> Symbol<'heap> { + sym::tmp } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { @@ -29,10 +28,9 @@ impl<'heap> StandardLibraryModule<'heap> for Tmp { // ::graph::tmp::decision_time_now() -> TimeAxis func( - lib, &mut def, - "::graph::tmp::decision_time_now", - &[], + sym::path::graph::tmp::decision_time_now, + [sym::decision_time_now], TypeDef { id: lib.ty.closure([] as [TypeId; 0], query_temporal_axes_ty.id), arguments: lib.ty.env.intern_generic_argument_references(&[]), diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/entity.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/entity.rs index dd5b0f19e26..c89d11c7ee5 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/entity.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/entity.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, @@ -373,7 +372,7 @@ pub(in crate::module::std_lib) struct Entity { impl<'heap> StandardLibraryModule<'heap> for Entity { type Children = (); - fn name(_: &'heap Heap) -> Symbol<'heap> { + fn name() -> Symbol<'heap> { sym::entity } diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/mod.rs index 7fd11736062..39359faff79 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/knowledge/mod.rs @@ -1,12 +1,11 @@ pub mod entity; use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{ModuleDef, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Knowledge; @@ -14,8 +13,8 @@ pub(in crate::module::std_lib) struct Knowledge; impl<'heap> StandardLibraryModule<'heap> for Knowledge { type Children = (self::entity::Entity,); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("knowledge") + fn name() -> Symbol<'heap> { + sym::knowledge } fn define(_: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/mod.rs index 9e743089972..3c6ae5eeabd 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/mod.rs @@ -1,11 +1,10 @@ // This is in a separate module, to facilitate: https://linear.app/hash/issue/H-4735/hashql-convert-rust-types-into-hashql-types use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{ModuleDef, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub mod knowledge; @@ -23,8 +22,8 @@ impl<'heap> StandardLibraryModule<'heap> for Types { self::principal::Principal, ); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("types") + fn name() -> Symbol<'heap> { + sym::types } fn define(_: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/entity_type.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/entity_type.rs index b8fb9b034ab..d7187cd2ebc 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/entity_type.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/entity_type.rs @@ -1,10 +1,9 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule, core::option::types::option}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct EntityType { @@ -17,8 +16,8 @@ pub(in crate::module::std_lib) struct EntityType { impl<'heap> StandardLibraryModule<'heap> for EntityType { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("entity_type") + fn name() -> Symbol<'heap> { + sym::entity_type } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/mod.rs index 8edd217d567..ed9b49b76a6 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/ontology/mod.rs @@ -1,3 +1,11 @@ +use crate::{ + module::{ + StandardLibrary, + std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, + }, + symbol::{Symbol, sym}, +}; + pub(in crate::module::std_lib) mod entity_type; pub mod types { @@ -50,15 +58,6 @@ pub mod types { } } -use crate::{ - heap::Heap, - module::{ - StandardLibrary, - std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, - }, - symbol::{Symbol, sym}, -}; - pub(in crate::module::std_lib) struct Ontology { _dependencies: (std_lib::core::url::Url,), } @@ -66,8 +65,8 @@ pub(in crate::module::std_lib) struct Ontology { impl<'heap> StandardLibraryModule<'heap> for Ontology { type Children = (self::entity_type::EntityType,); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("ontology") + fn name() -> Symbol<'heap> { + sym::ontology } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/mod.rs index 2134f79dc61..fdbbe962a42 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/mod.rs @@ -1,3 +1,11 @@ +use crate::{ + module::{ + StandardLibrary, + std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, + }, + symbol::{Symbol, sym}, +}; + pub mod web; pub mod types { @@ -25,15 +33,6 @@ pub mod types { } } -use crate::{ - heap::Heap, - module::{ - StandardLibrary, - std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, - }, - symbol::{Symbol, sym}, -}; - pub(in crate::module::std_lib) struct ActorGroup { _dependencies: (std_lib::core::uuid::Uuid,), } @@ -41,8 +40,8 @@ pub(in crate::module::std_lib) struct ActorGroup { impl<'heap> StandardLibraryModule<'heap> for ActorGroup { type Children = (self::web::Web,); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("actor_group") + fn name() -> Symbol<'heap> { + sym::actor_group } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/web.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/web.rs index fa3319c5865..18d196e246e 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/web.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/actor_group/web.rs @@ -1,5 +1,4 @@ use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{self, ItemDef, ModuleDef, StandardLibraryModule}, @@ -40,8 +39,8 @@ pub(in crate::module::std_lib) struct Web { impl<'heap> StandardLibraryModule<'heap> for Web { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("web") + fn name() -> Symbol<'heap> { + sym::web } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/mod.rs b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/mod.rs index ae6f9075180..82dbda1c525 100644 --- a/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/graph/types/principal/mod.rs @@ -1,12 +1,11 @@ pub mod actor_group; use crate::{ - heap::Heap, module::{ StandardLibrary, std_lib::{ModuleDef, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct Principal { @@ -16,8 +15,8 @@ pub(in crate::module::std_lib) struct Principal { impl<'heap> StandardLibraryModule<'heap> for Principal { type Children = (self::actor_group::ActorGroup,); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("principal") + fn name() -> Symbol<'heap> { + sym::principal } fn define(_: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/kernel/mod.rs b/libs/@local/hashql/core/src/module/std_lib/kernel/mod.rs index 51a6f8818e3..e7a23ecf350 100644 --- a/libs/@local/hashql/core/src/module/std_lib/kernel/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/kernel/mod.rs @@ -2,15 +2,15 @@ pub(in crate::module::std_lib) mod special_form; pub(in crate::module::std_lib) mod r#type; use super::{ModuleDef, StandardLibrary, StandardLibraryModule}; -use crate::{heap::Heap, symbol::Symbol}; +use crate::symbol::{Symbol, sym}; pub(in crate::module::std_lib) struct Kernel; impl<'heap> StandardLibraryModule<'heap> for Kernel { type Children = (self::special_form::SpecialForm, self::r#type::Type); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("kernel") + fn name() -> Symbol<'heap> { + sym::kernel } fn define(_: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { diff --git a/libs/@local/hashql/core/src/module/std_lib/kernel/special_form.rs b/libs/@local/hashql/core/src/module/std_lib/kernel/special_form.rs index 3e6f34b9f40..b7f981f273b 100644 --- a/libs/@local/hashql/core/src/module/std_lib/kernel/special_form.rs +++ b/libs/@local/hashql/core/src/module/std_lib/kernel/special_form.rs @@ -1,13 +1,10 @@ -use core::iter; - use crate::{ - heap::Heap, module::{ item::IntrinsicValueItem, locals::TypeDef, std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, }; pub(in crate::module::std_lib) struct SpecialForm; @@ -17,48 +14,51 @@ impl SpecialForm { lib: &StandardLibrary<'_, 'heap>, def: &mut ModuleDef<'heap>, - name: &'static str, - alias: &[&'static str], + path: Symbol<'heap>, + names: impl IntoIterator>, ) { let value = IntrinsicValueItem { - name, + name: path, r#type: TypeDef { id: lib.ty.never(), arguments: lib.ty.env.intern_generic_argument_references(&[]), }, }; - let ident = name.rsplit_once("::").expect("path should be non-empty").1; - - def.push_aliased( - iter::once(ident) - .chain(alias.iter().copied()) - .map(|name| lib.heap.intern_symbol(name)), - ItemDef::intrinsic(value), - ); + def.push_aliased(names, ItemDef::intrinsic(value)); } } impl<'heap> StandardLibraryModule<'heap> for SpecialForm { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("special_form") + fn name() -> Symbol<'heap> { + sym::special_form } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { let mut def = ModuleDef::new(); - Self::make(lib, &mut def, "::kernel::special_form::if", &[]); - Self::make(lib, &mut def, "::kernel::special_form::as", &[]); - Self::make(lib, &mut def, "::kernel::special_form::let", &[]); - Self::make(lib, &mut def, "::kernel::special_form::type", &[]); - Self::make(lib, &mut def, "::kernel::special_form::newtype", &[]); - Self::make(lib, &mut def, "::kernel::special_form::use", &[]); - Self::make(lib, &mut def, "::kernel::special_form::fn", &[]); - Self::make(lib, &mut def, "::kernel::special_form::input", &[]); - Self::make(lib, &mut def, "::kernel::special_form::access", &["."]); - Self::make(lib, &mut def, "::kernel::special_form::index", &["[]"]); + Self::make(lib, &mut def, sym::path::r#if, [sym::r#if]); + Self::make(lib, &mut def, sym::path::r#as, [sym::r#as]); + Self::make(lib, &mut def, sym::path::r#let, [sym::r#let]); + Self::make(lib, &mut def, sym::path::r#type, [sym::r#type]); + Self::make(lib, &mut def, sym::path::newtype, [sym::newtype]); + Self::make(lib, &mut def, sym::path::r#use, [sym::r#use]); + Self::make(lib, &mut def, sym::path::r#fn, [sym::r#fn]); + Self::make(lib, &mut def, sym::path::input, [sym::input]); + Self::make( + lib, + &mut def, + sym::path::access, + [sym::access, sym::symbol::dot], + ); + Self::make( + lib, + &mut def, + sym::path::index, + [sym::index, sym::symbol::brackets], + ); def } diff --git a/libs/@local/hashql/core/src/module/std_lib/kernel/type.rs b/libs/@local/hashql/core/src/module/std_lib/kernel/type.rs index 70221126878..c7ffaf4cb3f 100644 --- a/libs/@local/hashql/core/src/module/std_lib/kernel/type.rs +++ b/libs/@local/hashql/core/src/module/std_lib/kernel/type.rs @@ -1,10 +1,9 @@ use crate::{ - heap::Heap, module::{ item::IntrinsicTypeItem, std_lib::{ItemDef, ModuleDef, StandardLibrary, StandardLibraryModule}, }, - symbol::Symbol, + symbol::{Symbol, sym}, r#type::TypeId, }; @@ -14,54 +13,59 @@ impl Type { fn primitive<'heap>( lib: &StandardLibrary<'_, 'heap>, def: &mut ModuleDef<'heap>, - name: &'static str, + name: Symbol<'heap>, id: TypeId, ) -> usize { let item = ItemDef::r#type(lib.ty.env, id, &[]); - def.push(lib.heap.intern_symbol(name), item) + def.push(name, item) } fn intrinsic<'heap>( - lib: &StandardLibrary<'_, 'heap>, def: &mut ModuleDef<'heap>, - name: &'static str, + path: Symbol<'heap>, + ident: Symbol<'heap>, ) -> usize { - let item = ItemDef::intrinsic(IntrinsicTypeItem { name }); - - let ident = name.rsplit_once("::").expect("path should be non-empty").1; + let item = ItemDef::intrinsic(IntrinsicTypeItem { name: path }); - def.push(lib.heap.intern_symbol(ident), item) + def.push(ident, item) } } impl<'heap> StandardLibraryModule<'heap> for Type { type Children = (); - fn name(heap: &'heap Heap) -> Symbol<'heap> { - heap.intern_symbol("type") + fn name() -> Symbol<'heap> { + sym::r#type } fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap> { let mut def = ModuleDef::new(); - Self::primitive(lib, &mut def, "Boolean", lib.ty.boolean()); - Self::primitive(lib, &mut def, "Null", lib.ty.null()); - Self::primitive(lib, &mut def, "Number", lib.ty.number()); - Self::primitive(lib, &mut def, "Integer", lib.ty.integer()); + Self::primitive(lib, &mut def, sym::Boolean, lib.ty.boolean()); + Self::primitive(lib, &mut def, sym::Null, lib.ty.null()); + Self::primitive(lib, &mut def, sym::Number, lib.ty.number()); + Self::primitive(lib, &mut def, sym::Integer, lib.ty.integer()); // Natural does not yet exist, due to lack of support for refinements - Self::primitive(lib, &mut def, "String", lib.ty.string()); + Self::primitive(lib, &mut def, sym::String, lib.ty.string()); - let unknown = Self::primitive(lib, &mut def, "Unknown", lib.ty.unknown()); - def.alias(unknown, lib.heap.intern_symbol("?")); + let unknown = Self::primitive(lib, &mut def, sym::Unknown, lib.ty.unknown()); + def.alias(unknown, sym::symbol::question_mark); - let never = Self::primitive(lib, &mut def, "Never", lib.ty.never()); - def.alias(never, lib.heap.intern_symbol("!")); + let never = Self::primitive(lib, &mut def, sym::Never, lib.ty.never()); + def.alias(never, sym::symbol::exclamation); // Struct/Tuple are purposefully excluded, as they are // fundamental types and do not have any meaningful value constructors. // Union and Intersections are also excluded, as they have explicit constructors - Self::intrinsic(lib, &mut def, "::kernel::type::List"); - Self::intrinsic(lib, &mut def, "::kernel::type::Dict"); + Self::intrinsic(&mut def, sym::path::List, sym::List); + Self::intrinsic(&mut def, sym::path::Dict, sym::Dict); + + // Union and Intersection are both type intrinsics with an alias, they are only used during + // special form desurgaring. + let index = Self::intrinsic(&mut def, sym::path::Union, sym::Union); + def.alias(index, sym::symbol::pipe); + let index = Self::intrinsic(&mut def, sym::path::Intersection, sym::Intersection); + def.alias(index, sym::symbol::ampersand); def } diff --git a/libs/@local/hashql/core/src/module/std_lib/mod.rs b/libs/@local/hashql/core/src/module/std_lib/mod.rs index 447d3065775..d6631f3213c 100644 --- a/libs/@local/hashql/core/src/module/std_lib/mod.rs +++ b/libs/@local/hashql/core/src/module/std_lib/mod.rs @@ -2,7 +2,7 @@ pub mod core; pub mod graph; mod kernel; -use ::core::iter; +use ::core::{iter, num::NonZero}; use super::{ModuleId, ModuleRegistry, item::IntrinsicItem, locals::TypeDef}; use crate::{ @@ -185,7 +185,7 @@ impl<'env, 'heap> StandardLibrary<'env, 'heap> { &self.modules[index].1 } - fn build(&mut self, parent: ModuleId) -> ModuleId + fn build(&mut self, depth: NonZero, parent: ModuleId) -> ModuleId where M: StandardLibraryModule<'heap>, { @@ -214,8 +214,8 @@ impl<'env, 'heap> StandardLibrary<'env, 'heap> { } // create all the child modules - let children_names = M::Children::names(self.heap); - let children_modules = M::Children::modules(self, id.value()); + let children_names = M::Children::names(); + let children_modules = M::Children::modules(self, depth.saturating_add(1), id.value()); for (name, module) in children_names.into_iter().zip(children_modules) { output.push(Item { @@ -226,8 +226,9 @@ impl<'env, 'heap> StandardLibrary<'env, 'heap> { } PartialModule { - name: M::name(self.heap), + name: M::name(), parent, + depth, items: self.registry.intern_items(&output), } }) @@ -235,9 +236,11 @@ impl<'env, 'heap> StandardLibrary<'env, 'heap> { pub(super) fn register(&mut self) { type Root = (self::core::Core, self::kernel::Kernel, self::graph::Graph); + const ONE: NonZero = NonZero::new(1).unwrap(); - let roots: smallvec::SmallVec<_, 3> = - Root::modules(self, ModuleId::ROOT).into_iter().collect(); + let roots: smallvec::SmallVec<_, 3> = Root::modules(self, ONE, ModuleId::ROOT) + .into_iter() + .collect(); for id in roots { self.registry.register(id); @@ -248,10 +251,11 @@ impl<'env, 'heap> StandardLibrary<'env, 'heap> { trait Submodules<'heap> { const LENGTH: usize; - fn names(heap: &'heap Heap) -> impl IntoIterator>; + fn names() -> impl IntoIterator>; fn modules( lib: &mut StandardLibrary<'_, 'heap>, + depth: NonZero, parent: ModuleId, ) -> impl IntoIterator; } @@ -259,12 +263,13 @@ trait Submodules<'heap> { impl<'heap> Submodules<'heap> for () { const LENGTH: usize = 0; - fn names(_: &'heap Heap) -> impl IntoIterator> { + fn names() -> impl IntoIterator> { iter::empty() } fn modules( _: &mut StandardLibrary<'_, 'heap>, + _: NonZero, _: ModuleId, ) -> impl IntoIterator { iter::empty() @@ -291,17 +296,18 @@ macro_rules! impl_submodules { { const LENGTH: usize = ${count($item)}; - fn names(heap: &'heap Heap) -> impl IntoIterator> { - $(let $item = $item::name(heap);)* + fn names() -> impl IntoIterator> { + $(let $item = $item::name();)* [$($item),*] } fn modules( lib: &mut StandardLibrary<'_, 'heap>, + depth: NonZero, parent: ModuleId, ) -> impl IntoIterator { - $(let $item = lib.build::<$item>(parent);)* + $(let $item = lib.build::<$item>(depth, parent);)* [$($item),*] } @@ -314,7 +320,7 @@ impl_submodules!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); trait StandardLibraryModule<'heap>: 'static { type Children: Submodules<'heap>; - fn name(heap: &'heap Heap) -> Symbol<'heap>; + fn name() -> Symbol<'heap>; fn define(lib: &mut StandardLibrary<'_, 'heap>) -> ModuleDef<'heap>; } diff --git a/libs/@local/hashql/core/src/symbol/mod.rs b/libs/@local/hashql/core/src/symbol/mod.rs index 7ab38ad8f0d..15a53b1da66 100644 --- a/libs/@local/hashql/core/src/symbol/mod.rs +++ b/libs/@local/hashql/core/src/symbol/mod.rs @@ -45,7 +45,7 @@ use core::{ pub use self::lookup::SymbolLookup; use self::repr::{ConstantRepr, Repr}; pub(crate) use self::table::SymbolTable; -use crate::span::SpanId; +use crate::span::{SpanId, Spanned}; /// A predefined symbol that can be used in pattern matching. /// @@ -458,6 +458,27 @@ impl<'heap> Ident<'heap> { kind: IdentKind::Lexical, } } + + /// Drops the [`IdentKind`], keeping only the symbol and its span. + /// + /// # Examples + /// + /// ``` + /// # use hashql_core::symbol::{Ident, sym}; + /// # use hashql_core::span::SpanId; + /// let ident = Ident::synthetic(sym::foo); + /// let spanned = ident.symbol(); + /// + /// assert_eq!(spanned.span, SpanId::SYNTHETIC); + /// assert_eq!(spanned.value, sym::foo); + /// ``` + #[must_use] + pub const fn symbol(self) -> Spanned> { + Spanned { + span: self.span, + value: self.value, + } + } } impl AsRef for Ident<'_> { diff --git a/libs/@local/hashql/core/src/symbol/sym.rs b/libs/@local/hashql/core/src/symbol/sym.rs index 1379e05f3a2..965711af38f 100644 --- a/libs/@local/hashql/core/src/symbol/sym.rs +++ b/libs/@local/hashql/core/src/symbol/sym.rs @@ -1,4 +1,10 @@ -#![expect(non_upper_case_globals, non_snake_case, clippy::min_ident_chars)] +#![expect( + non_upper_case_globals, + non_snake_case, + clippy::min_ident_chars, + clippy::non_ascii_literal, + unused_imports +)] use super::Symbol; hashql_macros::define_symbols! { @@ -18,7 +24,11 @@ hashql_macros::define_symbols! { bit_shl, bit_shr, bit_xor, + bits, + bool, Boolean, + cbrt, + cmp, collect, Confidence, confidence, @@ -75,8 +85,8 @@ hashql_macros::define_symbols! { left_entity_provenance, left_entity_uuid, left_entity_web_id, - link_data, LeftClosedTemporalInterval, + link_data, LinkData, List, lt, @@ -125,18 +135,23 @@ hashql_macros::define_symbols! { r#type: "type", r#use: "use", R, - RightBoundedTemporalInterval, - S, record_id, RecordId, + rem, Result, right_entity_confidence, right_entity_id, right_entity_provenance, right_entity_uuid, right_entity_web_id, + RightBoundedTemporalInterval, + root, + S, + shl, + shr, Some, special_form, + sqrt, start, String, sub, @@ -165,6 +180,26 @@ hashql_macros::define_symbols! { VersionedUrl, web_id, WebId, + xor, + tail, + head, + entities, + is_of_type, + property, + body, + tmp, + decision_time_now, + graph, + types, + principal, + web, + actor_group, + ontology, + entity_type, + knowledge, + url, + result, + json, // [tidy] sort alphabetically end internal: { @@ -174,10 +209,15 @@ hashql_macros::define_symbols! { symbol: { // [tidy] sort alphabetically start ampamp: "&&", + upwards: "↑", + sqrt: "√", + cbrt: "∛", + percent: "%", ampersand: "&", arrow: "->", arrow_head: "|>", asterisk: "*", + asteriskasterisk: "**", exclamation: "!", excleq: "!=", brackets: "[]", @@ -221,10 +261,14 @@ hashql_macros::define_symbols! { path: { // [tidy] sort alphabetically start + access: "::kernel::special_form::access", ActorGroupEntityUuid: "::graph::types::principal::actor_group::ActorGroupEntityUuid", BaseUrl: "::graph::types::ontology::BaseUrl", Confidence: "::graph::types::knowledge::entity::Confidence", DecisionTime: "::graph::temporal::DecisionTime", + Dict: "::kernel::type::Dict", + Union: "::kernel::type::Union", + Intersection: "::kernel::type::Intersection", DraftId: "::graph::types::knowledge::entity::DraftId", Entity: "::graph::types::knowledge::entity::Entity", EntityEditionId: "::graph::types::knowledge::entity::EntityEditionId", @@ -239,11 +283,15 @@ hashql_macros::define_symbols! { graph_head_entities: "::graph::head::entities", graph_tail_collect: "::graph::tail::collect", InclusiveTemporalBound: "::graph::temporal::InclusiveTemporalBound", + index: "::kernel::special_form::index", InferredEntityProvenance: "::graph::types::knowledge::entity::InferredEntityProvenance", + input: "::kernel::special_form::input", Interval: "::graph::temporal::Interval", LeftClosedTemporalInterval: "::graph::temporal::LeftClosedTemporalInterval", LinkData: "::graph::types::knowledge::entity::LinkData", + List: "::kernel::type::List", main: "::main", + newtype: "::kernel::special_form::newtype", None: "::core::option::None", OntologyTypeVersion: "::graph::ontology::OntologyTypeVersion", OpenTemporalBound: "::graph::temporal::OpenTemporalBound", @@ -252,6 +300,12 @@ hashql_macros::define_symbols! { PinnedTransactionTimeTemporalAxes: "::graph::temporal::PinnedTransactionTimeTemporalAxes", PropertyObjectMetadata: "::graph::types::knowledge::entity::PropertyObjectMetadata", PropertyProvenance: "::graph::types::knowledge::entity::PropertyProvenance", + r#as: "::kernel::special_form::as", + r#fn: "::kernel::special_form::fn", + r#if: "::kernel::special_form::if", + r#let: "::kernel::special_form::let", + r#type: "::kernel::special_form::type", + r#use: "::kernel::special_form::use", RecordId: "::graph::types::knowledge::entity::RecordId", RightBoundedTemporalInterval: "::graph::temporal::RightBoundedTemporalInterval", Some: "::core::option::Some", @@ -264,6 +318,50 @@ hashql_macros::define_symbols! { Uuid: "::core::uuid::Uuid", VersionedUrl: "::graph::types::ontology::VersionedUrl", WebId: "::graph::types::principal::actor_group::web::WebId", + core: { + bits: { + and: "::core::bits::and", + or: "::core::bits::or", + xor: "::core::bits::xor", + not: "::core::bits::not", + shl: "::core::bits::shl", + shr: "::core::bits::shr", + }, + bool: { + not: "::core::bool::not", + and: "::core::bool::and", + or: "::core::bool::or", + }, + cmp: { + gt: "::core::cmp::gt", + lt: "::core::cmp::lt", + gte: "::core::cmp::gte", + lte: "::core::cmp::lte", + eq: "::core::cmp::eq", + ne: "::core::cmp::ne", + }, + math: { + add: "::core::math::add", + sub: "::core::math::sub", + mul: "::core::math::mul", + div: "::core::math::div", + rem: "::core::math::rem", + r#mod: "::core::math::mod", + pow: "::core::math::pow", + sqrt: "::core::math::sqrt", + cbrt: "::core::math::cbrt", + root: "::core::math::root", + } + }, + graph: { + entity: { + is_of_type: "::graph::entity::is_of_type", + property: "::graph::entity::property", + }, + tmp: { + decision_time_now: "::graph::tmp::decision_time_now", + } + } // [tidy] sort alphabetically end } } diff --git a/libs/@local/hashql/core/src/sync/lock.rs b/libs/@local/hashql/core/src/sync/lock.rs index 873634dfe54..56856249fb1 100644 --- a/libs/@local/hashql/core/src/sync/lock.rs +++ b/libs/@local/hashql/core/src/sync/lock.rs @@ -175,6 +175,7 @@ enum LockGuardInner<'lock, T, L: LockType> { /// /// assert_eq!(lock.lock().len(), 4); /// ``` +#[clippy::has_significant_drop] pub struct LockGuard<'lock, T, L: LockType> { inner: LockGuardInner<'lock, T, L>, } diff --git a/libs/@local/hashql/diagnostics/src/diagnostic/suggestion.rs b/libs/@local/hashql/diagnostics/src/diagnostic/suggestion.rs index c9d04101780..978e8d28102 100644 --- a/libs/@local/hashql/diagnostics/src/diagnostic/suggestion.rs +++ b/libs/@local/hashql/diagnostics/src/diagnostic/suggestion.rs @@ -168,6 +168,38 @@ impl Suggestions { } } + /// Collects patches from an iterator, returning [`None`] if the iterator is empty. + /// + /// Fallible counterpart to [`Suggestions::patch`] for dynamically computed patch sets. + /// + /// # Examples + /// + /// ``` + /// use hashql_diagnostics::{Patch, Suggestions}; + /// + /// // Non-empty iterator produces Some + /// let suggestions = Suggestions::try_from_iter([Patch::new(10..15, "corrected")]); + /// assert!(suggestions.is_some()); + /// + /// // Empty iterator produces None + /// let empty: Option> = Suggestions::try_from_iter(std::iter::empty()); + /// assert!(empty.is_none()); + /// ``` + pub fn try_from_iter(iter: I) -> Option + where + I: IntoIterator>, + { + let patches: Vec<_> = iter.into_iter().collect(); + if patches.is_empty() { + return None; + } + + Some(Self { + patches, + trailer: None, + }) + } + /// Adds an additional patch to this suggestions collection. /// /// # Examples @@ -245,9 +277,9 @@ impl Suggestions { .get(source_id) .ok_or(RenderError::SourceNotFound(source_id))?; - let mut snippet = Snippet::source(&*source.content).path(source.path.as_deref()); - for patch in chunk { + let mut snippet = Snippet::source(&*source.content).path(source.path.as_deref()); + let patch = patch.render(context).map_err(|error| match error { RenderError::SpanNotFound(None, span) => { RenderError::SpanNotFound(Some(source_id), span) @@ -256,10 +288,10 @@ impl Suggestions { | RenderError::SpanNotFound(Some(_), _) | RenderError::ConcreteSourceNotFound => error, })?; + snippet = snippet.patch(patch); + group = group.element(snippet); } - - group = group.element(snippet); } if let Some(trailer) = self.trailer.as_deref() { diff --git a/libs/@local/hashql/hir/src/lower/checking.rs b/libs/@local/hashql/hir/src/lower/checking.rs index eb29a56598f..3317317a225 100644 --- a/libs/@local/hashql/hir/src/lower/checking.rs +++ b/libs/@local/hashql/hir/src/lower/checking.rs @@ -52,7 +52,7 @@ use crate::{ pub struct TypeCheckingResidual<'heap> { pub inputs: FastHashMap, TypeId>, - pub intrinsics: HirIdMap<&'static str>, + pub intrinsics: HirIdMap>, } pub struct TypeChecking<'ctx, 'env, 'hir, 'heap> { @@ -60,7 +60,7 @@ pub struct TypeChecking<'ctx, 'env, 'hir, 'heap> { context: &'ctx mut HirContext<'hir, 'heap>, locals: VarIdMap>, - intrinsics: HirIdMap<&'static str>, + intrinsics: HirIdMap>, closures: HirIdMap, lattice: LatticeEnvironment<'env, 'heap>, diff --git a/libs/@local/hashql/hir/src/lower/inference.rs b/libs/@local/hashql/hir/src/lower/inference.rs index f616cb341d6..6e016592b3b 100644 --- a/libs/@local/hashql/hir/src/lower/inference.rs +++ b/libs/@local/hashql/hir/src/lower/inference.rs @@ -6,6 +6,7 @@ use hashql_core::{ locals::TypeDef, }, span::{SpanId, Spanned}, + symbol::Symbol, r#type::{ PartialType, TypeBuilder, TypeId, environment::{ @@ -46,14 +47,14 @@ use crate::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Local<'heap> { pub r#type: TypeDef<'heap>, - pub intrinsic: Option<&'static str>, + pub intrinsic: Option>, } // We do not persist the types into the `HirMap` *yet* as we haven't yet verified if they are // correct. pub struct TypeInferenceResidual<'heap> { pub locals: VarIdMap>, - pub intrinsics: HirIdMap<&'static str>, + pub intrinsics: HirIdMap>, pub closures: HirIdMap, } @@ -69,7 +70,7 @@ pub struct TypeInference<'ctx, 'env, 'hir, 'heap> { visited: HirIdSet, locals: VarIdMap>, - intrinsics: HirIdMap<&'static str>, + intrinsics: HirIdMap>, closures: HirIdMap, } diff --git a/libs/@local/hashql/hir/src/lower/mod.rs b/libs/@local/hashql/hir/src/lower/mod.rs index 8735c03dcee..fb89dcaa961 100644 --- a/libs/@local/hashql/hir/src/lower/mod.rs +++ b/libs/@local/hashql/hir/src/lower/mod.rs @@ -1,4 +1,4 @@ -use hashql_ast::lowering::ExtractedTypes; +use hashql_ast::lower::ExtractedTypes; use hashql_core::r#type::environment::Environment; use hashql_diagnostics::{DiagnosticIssues, StatusExt as _, Success}; diff --git a/libs/@local/hashql/hir/src/lower/specialization/error.rs b/libs/@local/hashql/hir/src/lower/specialization/error.rs index 7c7ea1a25cc..3e41f690b0e 100644 --- a/libs/@local/hashql/hir/src/lower/specialization/error.rs +++ b/libs/@local/hashql/hir/src/lower/specialization/error.rs @@ -3,6 +3,7 @@ use alloc::borrow::Cow; use hashql_core::{ pretty::{Formatter, RenderOptions}, span::SpanId, + symbol::Symbol, r#type::environment::Environment, }; use hashql_diagnostics::{ @@ -76,7 +77,7 @@ impl DiagnosticCategory for SpecializationDiagnosticCategory { /// in the specialization phase. pub(crate) fn unsupported_intrinsic( span: SpanId, - intrinsic_name: &str, + intrinsic_name: Symbol<'_>, issue_url: &str, ) -> SpecializationDiagnostic { let mut diagnostic = Diagnostic::new( @@ -110,7 +111,10 @@ pub(crate) fn unsupported_intrinsic( /// /// This indicates a compiler bug where an intrinsic that should be mapped is missing. #[coverage(off)] // compiler bugs should never be hit -pub(crate) fn unknown_intrinsic(span: SpanId, intrinsic_name: &str) -> SpecializationDiagnostic { +pub(crate) fn unknown_intrinsic( + span: SpanId, + intrinsic_name: Symbol<'_>, +) -> SpecializationDiagnostic { let mut diagnostic = Diagnostic::new( SpecializationDiagnosticCategory::UnknownIntrinsic, Severity::Bug, @@ -214,7 +218,10 @@ pub(crate) fn non_intrinsic_graph_operation<'heap>( /// /// This indicates a compiler bug where a graph intrinsic that should be mapped is missing. #[coverage(off)] // compiler bugs should never be hit -pub(crate) fn non_graph_intrinsic(span: SpanId, intrinsic_name: &str) -> SpecializationDiagnostic { +pub(crate) fn non_graph_intrinsic( + span: SpanId, + intrinsic_name: Symbol<'_>, +) -> SpecializationDiagnostic { let mut diagnostic = Diagnostic::new( SpecializationDiagnosticCategory::NonGraphIntrinsic, Severity::Bug, diff --git a/libs/@local/hashql/hir/src/lower/specialization/mod.rs b/libs/@local/hashql/hir/src/lower/specialization/mod.rs index 055c57964a4..788d449c46c 100644 --- a/libs/@local/hashql/hir/src/lower/specialization/mod.rs +++ b/libs/@local/hashql/hir/src/lower/specialization/mod.rs @@ -5,6 +5,7 @@ use core::convert::Infallible; use hashql_core::{ collections::{FastHashMap, HashMapExt as _, SmallVec}, span::Spanned, + symbol::{Symbol, sym}, r#type::environment::Environment, }; @@ -35,7 +36,7 @@ pub struct Specialization<'ctx, 'env, 'hir, 'heap, 'diag> { env: &'env Environment<'heap>, context: &'ctx mut HirContext<'hir, 'heap>, - intrinsics: HirIdMap<&'static str>, + intrinsics: HirIdMap>, current: HirPtr, visited: HirIdMap>, @@ -47,7 +48,7 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di pub fn new( env: &'env Environment<'heap>, context: &'ctx mut HirContext<'hir, 'heap>, - intrinsics: HirIdMap<&'static str>, + intrinsics: HirIdMap>, diagnostics: &'diag mut LoweringDiagnosticIssues, ) -> Self { Self { @@ -71,11 +72,11 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di fn fold_call_into_graph_read( &mut self, call: Call<'heap>, - intrinsic: &'static str, + intrinsic: Symbol<'heap>, ) -> Option> { // The first argument is always the graph we're referring to. - let tail = match intrinsic { - "::graph::tail::collect" => GraphReadTail::Collect, + let tail = match intrinsic.as_constant() { + Some(sym::path::graph_tail_collect::CONST) => GraphReadTail::Collect, _ => unreachable!(), }; @@ -105,8 +106,8 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di return None; }; - match intrinsic { - "::graph::body::filter" => { + match intrinsic.as_constant() { + Some(sym::path::graph_body_filter::CONST) => { let &[follow, closure] = &*call.arguments else { unreachable!() }; @@ -114,7 +115,7 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di body.push(GraphReadBody::Filter(closure.value)); next = follow.value; } - "::graph::head::entities" => { + Some(sym::path::graph_head_entities::CONST) => { let head = GraphReadHead::Entity { axis: call.arguments[0].value, }; @@ -137,10 +138,11 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di } } + #[expect(clippy::too_many_lines, reason = "just a large match statement")] fn fold_intrinsic( &mut self, call: Call<'heap>, - intrinsic: &'static str, + intrinsic: Symbol<'heap>, ) -> >::Output>> { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] enum OpKind { @@ -148,11 +150,19 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di } #[expect(clippy::match_same_arms)] - let op = match intrinsic { - "::core::math::add" | "::core::math::sub" | "::core::math::mul" - | "::core::math::div" | "::core::math::rem" | "::core::math::mod" - | "::core::math::pow" | "::core::math::sqrt" | "::core::math::cbrt" - | "::core::math::root" => { + let op = match intrinsic.as_constant() { + Some( + sym::path::core::math::add::CONST + | sym::path::core::math::sub::CONST + | sym::path::core::math::mul::CONST + | sym::path::core::math::div::CONST + | sym::path::core::math::rem::CONST + | sym::path::core::math::r#mod::CONST + | sym::path::core::math::pow::CONST + | sym::path::core::math::sqrt::CONST + | sym::path::core::math::cbrt::CONST + | sym::path::core::math::root::CONST, + ) => { self.push_diagnostic(unsupported_intrinsic( call.function.span, intrinsic, @@ -161,8 +171,14 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di return Ok(None); } - "::core::bits::and" | "::core::bits::or" | "::core::bits::xor" - | "::core::bits::not" | "::core::bits::shl" | "::core::bits::shr" => { + Some( + sym::path::core::bits::and::CONST + | sym::path::core::bits::or::CONST + | sym::path::core::bits::xor::CONST + | sym::path::core::bits::not::CONST + | sym::path::core::bits::shl::CONST + | sym::path::core::bits::shr::CONST, + ) => { self.push_diagnostic(unsupported_intrinsic( call.function.span, intrinsic, @@ -171,13 +187,13 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di return Ok(None); } - "::core::cmp::gt" => OpKind::Bin(BinOp::Gt), - "::core::cmp::lt" => OpKind::Bin(BinOp::Lt), - "::core::cmp::gte" => OpKind::Bin(BinOp::Gte), - "::core::cmp::lte" => OpKind::Bin(BinOp::Lte), - "::core::cmp::eq" => OpKind::Bin(BinOp::Eq), - "::core::cmp::ne" => OpKind::Bin(BinOp::Ne), - "::core::bool::not" => { + Some(sym::path::core::cmp::gt::CONST) => OpKind::Bin(BinOp::Gt), + Some(sym::path::core::cmp::lt::CONST) => OpKind::Bin(BinOp::Lt), + Some(sym::path::core::cmp::gte::CONST) => OpKind::Bin(BinOp::Gte), + Some(sym::path::core::cmp::lte::CONST) => OpKind::Bin(BinOp::Lte), + Some(sym::path::core::cmp::eq::CONST) => OpKind::Bin(BinOp::Eq), + Some(sym::path::core::cmp::ne::CONST) => OpKind::Bin(BinOp::Ne), + Some(sym::path::core::bool::not::CONST) => { self.push_diagnostic(unsupported_intrinsic( call.function.span, intrinsic, @@ -186,17 +202,17 @@ impl<'ctx, 'env, 'hir, 'heap, 'diag> Specialization<'ctx, 'env, 'hir, 'heap, 'di return Ok(None); } - "::core::bool::and" => OpKind::Bin(BinOp::And), - "::core::bool::or" => OpKind::Bin(BinOp::Or), - "::graph::head::entities" | "::graph::body::filter" => { + Some(sym::path::core::bool::and::CONST) => OpKind::Bin(BinOp::And), + Some(sym::path::core::bool::or::CONST) => OpKind::Bin(BinOp::Or), + Some(sym::path::graph_head_entities::CONST | sym::path::graph_body_filter::CONST) => { // We ignore this on purpose, as `graph::tail::collect` will process these return Ok(None); } - "::graph::tmp::decision_time_now" => { + Some(sym::path::graph::tmp::decision_time_now::CONST) => { // currently a stand-in and not specialized in any way return Ok(None); } - "::graph::tail::collect" => { + Some(sym::path::graph_tail_collect::CONST) => { let Some(read) = self.fold_call_into_graph_read(call, intrinsic) else { return Ok(None); }; @@ -290,7 +306,7 @@ impl<'heap> Fold<'heap> for Specialization<'_, '_, '_, 'heap, '_> { // We need to check **before** folding the call, if the function is an intrinsic, otherwise // the underlying HirId might've been changed let intrinsic_node = if let NodeKind::Call(call) = node.kind - && let Some(intrinsic) = self.intrinsics.get(&call.function.id) + && let Some(&intrinsic) = self.intrinsics.get(&call.function.id) { self.fold_intrinsic(call, intrinsic)? } else { diff --git a/libs/@local/hashql/hir/src/reify/mod.rs b/libs/@local/hashql/hir/src/reify/mod.rs index 38dca9faa87..f02073485cf 100644 --- a/libs/@local/hashql/hir/src/reify/mod.rs +++ b/libs/@local/hashql/hir/src/reify/mod.rs @@ -3,7 +3,7 @@ pub mod error; use core::mem; use hashql_ast::{ - lowering::ExtractedTypes, + lower::ExtractedTypes, node::{ expr::{ AsExpr, CallExpr, ClosureExpr, DictExpr, Expr, ExprKind, FieldExpr, IfExpr, IndexExpr, @@ -71,7 +71,7 @@ impl<'heap> ReificationContext<'_, '_, '_, 'heap> { let mut arguments = SmallVec::with_capacity(args.len()); for argument in args { - let Some(value) = self.expr(*argument.value) else { + let Some(value) = self.expr(argument.value) else { incomplete = true; continue; }; @@ -170,7 +170,7 @@ impl<'heap> ReificationContext<'_, '_, '_, 'heap> { let mut fields = SmallVec::with_capacity(len); for (index, element) in elements.into_iter().enumerate() { - let Some(field) = self.expr(*element.value) else { + let Some(field) = self.expr(element.value) else { continue; }; @@ -244,7 +244,7 @@ impl<'heap> ReificationContext<'_, '_, '_, 'heap> { let mut fields = SmallVec::with_capacity(len); for (index, entry) in entries.into_iter().enumerate() { - let Some(value) = self.expr(*entry.value) else { + let Some(value) = self.expr(entry.value) else { continue; }; @@ -819,14 +819,6 @@ impl<'heap> ReificationContext<'_, '_, '_, 'heap> { return None; } - ExprKind::Use(_) => { - self.diagnostics.push(unprocessed_expression( - expr.span, - "use declaration", - "import resolution", - )); - return None; - } ExprKind::Input(input) => (input.span, self.input_expr(hir_id, input)?), ExprKind::Closure(closure) => (closure.span, self.closure_expr(hir_id, closure)?), ExprKind::If(r#if) => (r#if.span, self.if_expr(r#if)?), diff --git a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/complex-mixed.stdout b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/complex-mixed.stdout index 07a66df61aa..00fccd48adf 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/complex-mixed.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/complex-mixed.stdout @@ -4,7 +4,7 @@ let a:0 = 10, b:0 = ::core::math::add(a:0, 5), c:0 = a:0, d:0 = b:0, - e:0 = a:0 + e:0 = c:0 in ::core::math::add(::core::math::mul(d:0, e:0), ::core::math::sub(a:0, b:0)) diff --git a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/generics.jsonc b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/generics.jsonc index 65ae8d27030..d865309f45a 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/generics.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/generics.jsonc @@ -1,3 +1,3 @@ //@ run: pass //@ description: Alias replacement with generics should work -["let", "foo", "core::math::add", ["let", "bar", "foo", "bar"]] +["let", "foo", "::core::math::add", ["let", "bar", "foo", "bar"]] diff --git a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/nested-aliases.stdout b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/nested-aliases.stdout index 8ba50a1a601..3e45f1b4bb9 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/nested-aliases.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/nested-aliases.stdout @@ -2,7 +2,7 @@ let x:0 = 42, y:0 = x:0, - z:0 = x:0 + z:0 = y:0 in ::core::math::add(z:0, x:0) diff --git a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/qualified-variable-alias.stdout b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/qualified-variable-alias.stdout index 2567529a43e..b47a924b1cc 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/qualified-variable-alias.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/alias-replacement/qualified-variable-alias.stdout @@ -3,7 +3,7 @@ let x:0 = ::core::math::add, y:0 = ::core::math::add in -::core::math::mul(y:0, 2) +::core::math::mul(::core::math::add, 2) ════ HIR after alias replacement ═══════════════════════════════════════════════ diff --git a/libs/@local/hashql/hir/tests/ui/lower/checking/bind-arguments.jsonc b/libs/@local/hashql/hir/tests/ui/lower/checking/bind-arguments.jsonc index 749fee216f2..b92706e2b1f 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/checking/bind-arguments.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/checking/bind-arguments.jsonc @@ -1,3 +1,7 @@ //@ run: pass //@ description: Verifies type checking for binding specific types (Number, Integer) to a generic function call. -["`+`", { "#literal": 42.12 }, { "#literal": 42 }] +[ + "::core::math::add", + { "#literal": 42.12 }, + { "#literal": 42 } +] diff --git a/libs/@local/hashql/hir/tests/ui/lower/ctor/too-many-arguments.jsonc b/libs/@local/hashql/hir/tests/ui/lower/ctor/too-many-arguments.jsonc index 2207f9f53f8..5b880a62803 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/ctor/too-many-arguments.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/ctor/too-many-arguments.jsonc @@ -6,7 +6,7 @@ { "#struct": { "name": "T", "age": "U" } }, [ "Person", - //~^ ERROR Remove this argument + //~^ ERROR requires 2 generic arguments, but 3 were provided { "#literal": 30 } ] ] diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments-explicit.jsonc b/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments-explicit.jsonc index 7cef10325e9..b134fe4deef 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments-explicit.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments-explicit.jsonc @@ -2,7 +2,7 @@ //@ description: Ensures type inference fails when explicitly bound generic arguments conflict with provided argument types. [ "`+`", - //~^ ERROR Type variable has incompatible upper and lower bounds + //~^ ERROR Type bound constraint violation { "#literal": 42.12 }, { "#literal": "42" } ] diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments.jsonc b/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments.jsonc index a3e09ac8a7a..ffc2b7c5438 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/bind-arguments.jsonc @@ -1,3 +1,7 @@ //@ run: pass //@ description: Verifies type inference for explicitly binding generic arguments (Number, Integer) in a function call. -["`+`", { "#literal": 42.12 }, { "#literal": 42 }] +[ + "::core::math::add", + { "#literal": 42.12 }, + { "#literal": 42 } +] diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.jsonc b/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.jsonc index 6700778b7d3..fdc7475f12b 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.jsonc +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.jsonc @@ -1,13 +1,18 @@ //@ run: pass //@ description: Test that inference in argument position works correctly [ + "use", + "::core::math", + "*", [ - "fn", - { "#tuple": [] }, - { "#struct": { "lhs": "_", "rhs": "_" } }, - "_", - ["+", "lhs", "rhs"] - ], - { "#literal": 2 }, - { "#literal": 3 } + [ + "fn", + { "#tuple": [] }, + { "#struct": { "lhs": "_", "rhs": "_" } }, + "_", + ["+", "lhs", "rhs"] + ], + { "#literal": 2 }, + { "#literal": 3 } + ] ] diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.stdout b/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.stdout index 2f134281bbc..10324b06840 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/infer-argument.stdout @@ -1,23 +1,23 @@ ════ Initial HIR ═══════════════════════════════════════════════════════════════ -(lhs:0: _0, rhs:0: _1): _2 -> ::core::math::add(lhs:0, rhs:0)(2, 3) +(lhs:0: _0, rhs:0: _1): _2 -> ::core::math::+(lhs:0, rhs:0)(2, 3) ════ HIR after type inference ══════════════════════════════════════════════════ -(lhs:0: _0, rhs:0: _1): _2 -> ::core::math::add(lhs:0, rhs:0)(2, 3) +(lhs:0: _0, rhs:0: _1): _2 -> ::core::math::+(lhs:0, rhs:0)(2, 3) ════ Types ═════════════════════════════════════════════════════════════════════ -┌─ (lhs:0: _0, rhs:0: _1): _2 -> ::core::math::add(lhs:0, rhs:0)(2, 3) +┌─ (lhs:0: _0, rhs:0: _1): _2 -> ::core::math::+(lhs:0, rhs:0)(2, 3) └→ _4«Integer» -┌─ (lhs:0: _0, rhs:0: _1): _2 -> ::core::math::add(lhs:0, rhs:0) +┌─ (lhs:0: _0, rhs:0: _1): _2 -> ::core::math::+(lhs:0, rhs:0) └→ (_0«Integer», _1«Integer») -> _2«Integer» -┌─ ::core::math::add(lhs:0, rhs:0) +┌─ ::core::math::+(lhs:0, rhs:0) └→ _3«Integer» -┌─ ::core::math::add +┌─ ::core::math::+ └→ (T?29«Integer», U?30«Integer») -> (T?29«Integer» | U?30«Integer») diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/assignment.stdout b/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/assignment.stdout index 4be6b8ab435..72fc653a599 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/assignment.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/assignment.stdout @@ -1,16 +1,16 @@ ════ Initial HIR ═══════════════════════════════════════════════════════════════ -let add:0 = ::core::math::add in add:0 +let add:0 = ::core::math::add in ::core::math::add ════ HIR after type inference ══════════════════════════════════════════════════ -let add:0 = ::core::math::add in add:0 +let add:0 = ::core::math::add in ::core::math::add ════ Intrinsics ════════════════════════════════════════════════════════════════ ┌─ ::core::math::add └→ ::core::math::add -┌─ add:0 +┌─ ::core::math::add └→ ::core::math::add diff --git a/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/re-assignment.stdout b/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/re-assignment.stdout index 91acdeabe81..2d34868739b 100644 --- a/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/re-assignment.stdout +++ b/libs/@local/hashql/hir/tests/ui/lower/inference/intrinsics/re-assignment.stdout @@ -3,14 +3,14 @@ let foo:0 = ::core::math::add, bar:0 = ::core::math::add in -bar:0 +::core::math::add ════ HIR after type inference ══════════════════════════════════════════════════ let foo:0 = ::core::math::add, bar:0 = ::core::math::add in -bar:0 +::core::math::add ════ Intrinsics ════════════════════════════════════════════════════════════════ @@ -20,6 +20,6 @@ bar:0 ┌─ ::core::math::add └→ ::core::math::add -┌─ bar:0 +┌─ ::core::math::add └→ ::core::math::add diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/array/mod.rs b/libs/@local/hashql/syntax-jexpr/src/parser/array/mod.rs index 001572ca59a..6915506fd1b 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/array/mod.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/array/mod.rs @@ -60,28 +60,25 @@ fn parse_labelled_argument_shorthand<'heap>( value: Argument { id: NodeId::PLACEHOLDER, span: token_span, - value: Box::new_in( - Expr { + value: Expr { + id: NodeId::PLACEHOLDER, + span: token_span, + kind: ExprKind::Path(Path { id: NodeId::PLACEHOLDER, span: token_span, - kind: ExprKind::Path(Path { - id: NodeId::PLACEHOLDER, - span: token_span, - rooted: false, - segments: { - let mut segments = Vec::with_capacity_in(1, state.heap()); - segments.push(PathSegment { - id: NodeId::PLACEHOLDER, - span: token_span, - name: key, - arguments: Vec::new_in(state.heap()), - }); - segments - }, - }), - }, - state.heap(), - ), + rooted: false, + segments: { + let mut segments = Vec::with_capacity_in(1, state.heap()); + segments.push(PathSegment { + id: NodeId::PLACEHOLDER, + span: token_span, + name: key, + arguments: Vec::new_in(state.heap()), + }); + segments + }, + }), + }, }, }]) } @@ -163,7 +160,7 @@ fn parse_labelled_argument<'heap>( value: Argument { id: NodeId::PLACEHOLDER, span: value.span, - value: Box::new_in(value, state.heap()), + value, }, }); @@ -208,7 +205,7 @@ pub(crate) fn parse_array<'heap, 'source>( arguments.push(Argument { id: NodeId::PLACEHOLDER, span: expr.span, - value: Box::new_in(expr, state.heap()), + value: expr, }); } function @ None => *function = Some(parse_expr(state)?), diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/object/struct.rs b/libs/@local/hashql/syntax-jexpr/src/parser/object/struct.rs index b2386cc31a2..224ba0724dd 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/object/struct.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/object/struct.rs @@ -115,7 +115,7 @@ fn parse_struct<'heap>( id: NodeId::PLACEHOLDER, span: state.insert_range(key.span.cover(value_span)), key: ident, - value: Box::new_in(value, state.heap()), + value, }); Ok(()) diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/object/tuple.rs b/libs/@local/hashql/syntax-jexpr/src/parser/object/tuple.rs index ff1ac458976..d7701dbd064 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/object/tuple.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/object/tuple.rs @@ -96,7 +96,7 @@ fn parse_tuple<'heap>( let element = TupleElement { id: NodeId::PLACEHOLDER, span: expr.span, - value: Box::new_in(expr, state.heap()), + value: expr, }; elements.push(element); diff --git a/libs/@local/hashql/syntax-jexpr/src/parser/string/generic.rs b/libs/@local/hashql/syntax-jexpr/src/parser/string/generic.rs index 8ca09096184..46982974264 100644 --- a/libs/@local/hashql/syntax-jexpr/src/parser/string/generic.rs +++ b/libs/@local/hashql/syntax-jexpr/src/parser/string/generic.rs @@ -30,7 +30,7 @@ where Ok(GenericArgument { id: NodeId::PLACEHOLDER, span: input.state.span(span), - r#type: Box::new_in(r#type, input.state.heap), + r#type, }) } @@ -50,7 +50,7 @@ where id: NodeId::PLACEHOLDER, span: input.state.span(span), name, - bound: Some(Box::new_in(bound, input.state.heap)), + bound: Some(bound), }) } @@ -70,7 +70,7 @@ where id: NodeId::PLACEHOLDER, span: input.state.span(span), name, - bound: bound.map(|bound| Box::new_in(bound, input.state.heap)), + bound, }) }