Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ resolver = "2"
ra_ap_proc_macro_api = { path = "crates/third_party/ra_ap_proc_macro_api" }

[workspace.package]
version = "0.3.0-rc8"
version = "0.3.0-rc9"
description = "The Incan programming language compiler"
edition = "2024"
rust-version = "1.92"
Expand Down
14 changes: 12 additions & 2 deletions src/backend/ir/lower/decl/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ impl AstLowering {
format!("__incan_decorated_{name}")
}

/// Return the span used for synthetic decorator callee nodes.
///
/// The full decorator factory call keeps the source decorator span for typechecker handoff. Nested synthetic
/// callees must not reuse that span because expression metadata is span-keyed and the factory result type would
/// otherwise overwrite the callee's callable signature during lowering.
pub(in crate::backend::ir::lower) fn decorator_synthetic_callee_span() -> ast::Span {
ast::Span::default()
}

/// Build an expression that resolves a decorator's path through ordinary expression lowering.
pub(in crate::backend::ir::lower) fn decorator_path_expr(
decorator: &ast::Decorator,
Expand Down Expand Up @@ -220,14 +229,15 @@ impl AstLowering {
is_absolute: decorator.node.path.is_absolute,
segments: path[..path.len() - 1].to_vec(),
};
let base = Self::decorator_path_expr_from_import_path(&base_path, decorator.span);
let base =
Self::decorator_path_expr_from_import_path(&base_path, Self::decorator_synthetic_callee_span());
let method = path.last().cloned().unwrap_or_default();
Spanned::new(
Expr::MethodCall(Box::new(base), method, Vec::new(), args),
decorator.span,
)
} else {
let callee = Self::decorator_path_expr(&decorator.node, decorator.span);
let callee = Self::decorator_path_expr(&decorator.node, Self::decorator_synthetic_callee_span());
Spanned::new(Expr::Call(Box::new(callee), Vec::new(), args), decorator.span)
}
} else {
Expand Down
5 changes: 3 additions & 2 deletions src/backend/ir/lower/decl/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ impl AstLowering {
is_absolute: decorator.node.path.is_absolute,
segments: path[..path.len() - 1].to_vec(),
};
let base = Self::decorator_path_expr_from_import_path(&base_path, decorator.span);
let base =
Self::decorator_path_expr_from_import_path(&base_path, Self::decorator_synthetic_callee_span());
let method_name = path.last().cloned().unwrap_or_default();
Spanned::new(
ast::Expr::MethodCall(Box::new(base), method_name, Vec::new(), args),
decorator.span,
)
} else {
let callee = Self::decorator_path_expr(&decorator.node, decorator.span);
let callee = Self::decorator_path_expr(&decorator.node, Self::decorator_synthetic_callee_span());
Spanned::new(ast::Expr::Call(Box::new(callee), Vec::new(), args), decorator.span)
}
} else {
Expand Down
66 changes: 66 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9107,6 +9107,72 @@ def test_imported_const_str_call_arguments_materialize() -> None:
);
}

#[test]
fn e2e_imported_decorator_factory_const_str_argument_materializes() {
let dir = write_test_project(
"incan.toml",
r#"[project]
name = "imported_decorator_const_str_materialization"
version = "0.1.0"
"#,
);
let src_dir = dir.join("src");
let tests_dir = dir.join("tests");

if let Err(err) = std::fs::create_dir_all(&src_dir) {
panic!("failed to create src dir: {}", err);
}
if let Err(err) = std::fs::create_dir_all(&tests_dir) {
panic!("failed to create tests dir: {}", err);
}
if let Err(err) = std::fs::write(
src_dir.join("registry.incn"),
r#"
pub const TOKEN: str = "probe.value"

def keep_int(func: (int) -> int) -> (int) -> int:
return func

pub def registered(_name: str) -> Callable[(int) -> int, (int) -> int]:
return keep_int
"#,
) {
panic!("failed to write registry source: {}", err);
}
if let Err(err) = std::fs::write(
tests_dir.join("test_imported_decorator_const_str.incn"),
r#"
from std.testing import assert_eq
from registry import TOKEN, registered

@registered(TOKEN)
def increment(value: int) -> int:
return value + 1

def test_imported_decorator_factory_const_str_argument_materializes() -> None:
assert_eq(increment(1), 2)
"#,
) {
panic!("failed to write imported decorator const string test: {}", err);
}

let output = run_incan_test(&dir);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);

assert!(
output.status.success(),
"expected imported decorator factory const str materialization test to succeed.\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr,
);
assert!(
!stderr.contains("expected `String`, found `&str`"),
"decorator factory const str argument should materialize as an owned string.\nstderr:\n{}",
stderr,
);
}

#[test]
fn e2e_empty_list_arguments_in_tests_preserve_string_element_type() -> Result<(), Box<dyn std::error::Error>> {
let dir = write_test_project(
Expand Down
Loading