Skip to content

Commit 3849289

Browse files
authored
Merge pull request #1370 from harehare/feat/mq-check-piped-self-constraint
feat(mq-check): support piped builtin calls and self keyword type propagation
2 parents fe2fc86 + 5f1e7b0 commit 3849289

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,11 @@ This makes it easy to add your own tools and workflows to `mq` without modifying
370370

371371
The following external tools are available to extend mq's functionality:
372372

373-
- [mq-check](https://github.com/harehare/mq-check) - A syntax and semantic checker for mq files.
373+
- [mq-check](https://github.com/harehare/mq/tree/main/crates/mq-check) - A syntax and semantic checker for mq files.
374374
- [mq-conv](https://github.com/harehare/mq-conv) - A CLI tool for converting various file formats to Markdown.
375375
- [mq-docs](https://github.com/harehare/mq-docs) - A documentation generator for mq functions, macros, and selectors.
376376
- [mq-edit](https://github.com/harehare/mq-edit) - A terminal-based Markdown and code editor with WYSIWYG rendering and LSP support.
377+
- [mq-lsp](https://github.com/harehare/mq/tree/main/crates/mq-lsp) - Language Server Protocol (LSP) implementation for mq query files, providing IDE features like completion, hover, and diagnostics.
377378
- [mq-mcp](https://github.com/harehare/mq-mcp) - Model Context Protocol (MCP) server implementation for AI assistants
378379
- [mq-task](https://github.com/harehare/mq-task) - Task runner using mq for Markdown-based task definitions
379380
- [mq-tui](https://github.com/harehare/mq-tui) - Terminal User Interface (TUI) for interactive mq query

crates/mq-check/src/constraint.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ fn might_receive_piped_input(hir: &Hir, symbol_id: SymbolId) -> bool {
442442
};
443443
if matches!(
444444
parent.kind,
445-
SymbolKind::Block | SymbolKind::Function(_) | SymbolKind::Macro(_)
445+
SymbolKind::Block | SymbolKind::Function(_) | SymbolKind::Macro(_) | SymbolKind::Call
446446
) {
447447
return true;
448448
}
@@ -2106,6 +2106,14 @@ fn generate_symbol_constraints(
21062106
let ty_var = ctx.fresh_var();
21072107
ctx.set_symbol_type(symbol_id, Type::Var(ty_var));
21082108
}
2109+
} else if symbol.is_some_and(|s| s.value.as_deref() == Some("self")) {
2110+
// `self` refers to the piped input value
2111+
if let Some(piped_ty) = ctx.get_piped_input(symbol_id).cloned() {
2112+
ctx.set_symbol_type(symbol_id, piped_ty);
2113+
} else {
2114+
let ty_var = ctx.fresh_var();
2115+
ctx.set_symbol_type(symbol_id, Type::Var(ty_var));
2116+
}
21092117
} else {
21102118
let ty_var = ctx.fresh_var();
21112119
ctx.set_symbol_type(symbol_id, Type::Var(ty_var));

crates/mq-check/tests/integration_test.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,3 +1380,45 @@ fn test_type_narrowing(#[case] code: &str, #[case] should_succeed: bool, #[case]
13801380
result
13811381
);
13821382
}
1383+
1384+
#[test]
1385+
fn test_piped_builtin_call_in_argument_position() {
1386+
// Builtin calls with no explicit args used as arguments to higher-order functions
1387+
// should not error — the parent function will pipe each element at runtime.
1388+
assert!(
1389+
check_types("[[1,2],[3,4]] | map(first())").is_empty(),
1390+
"map(first()) should not error"
1391+
);
1392+
assert!(
1393+
check_types("[[1,2],[3,4]] | map(last())").is_empty(),
1394+
"map(last()) should not error"
1395+
);
1396+
}
1397+
1398+
#[rstest]
1399+
#[case(r#""hello" | self | upcase()"#, "self should preserve string type for upcase", true)]
1400+
#[case("[1,2,3] | self | first()", "self should preserve array type for first()", true)]
1401+
#[case(
1402+
r#""hello" | self | sort"#,
1403+
"string piped through self then sort (array-only) should error",
1404+
false
1405+
)]
1406+
#[case(
1407+
r#""hello" | self | sort()"#,
1408+
"string piped through self then sort (array-only) should error",
1409+
false
1410+
)]
1411+
#[case(
1412+
"42 | self | sort()",
1413+
"number piped through self then sort (array-only) should error",
1414+
false
1415+
)]
1416+
#[case(
1417+
r#""hello" | upcase | sort"#,
1418+
"string piped through string op then sort (array-only) should error",
1419+
false
1420+
)]
1421+
fn test_self_keyword_preserves_piped_type(#[case] code: &str, #[case] description: &str, #[case] should_succeed: bool) {
1422+
// `self` should unify with the piped input type
1423+
assert_eq!(check_types(code).is_empty(), should_succeed, "{}", description);
1424+
}

0 commit comments

Comments
 (0)