From bb492ec62396d37e0b5cb0fe79a42e8e1e5ed4b2 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 3 Jun 2026 13:22:40 +0300 Subject: [PATCH] Do not emit a "type annotations needed" error on `include_bytes!()` where the array length cannot be inferred --- crates/hir-def/src/expr_store.rs | 1 + crates/hir-def/src/expr_store/lower.rs | 1 + crates/hir-def/src/expr_store/pretty.rs | 1 + crates/hir-def/src/hir.rs | 4 +- crates/hir-expand/src/builtin/fn_macro.rs | 12 +--- .../closure/analysis/expr_use_visitor.rs | 2 + crates/hir-ty/src/infer/expr.rs | 8 ++- crates/hir-ty/src/infer/mutability.rs | 3 +- crates/hir-ty/src/mir/lower.rs | 1 + crates/ide-db/src/syntax_helpers/node_ext.rs | 3 +- .../src/handlers/type_must_be_known.rs | 13 +++++ crates/parser/src/grammar/expressions/atom.rs | 4 ++ crates/parser/src/syntax_kind/generated.rs | 8 +++ crates/parser/test_data/generated/runner.rs | 2 + .../parser/inline/ok/include_bytes.rast | 23 ++++++++ .../parser/inline/ok/include_bytes.rs | 1 + crates/syntax/rust.ungram | 6 ++ crates/syntax/src/ast/generated/nodes.rs | 58 +++++++++++++++++++ crates/syntax/src/ast/prec.rs | 10 ++-- xtask/src/codegen/grammar/ast_src.rs | 1 + 20 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 crates/parser/test_data/parser/inline/ok/include_bytes.rast create mode 100644 crates/parser/test_data/parser/inline/ok/include_bytes.rs diff --git a/crates/hir-def/src/expr_store.rs b/crates/hir-def/src/expr_store.rs index 8768413ce554..6df95e1a787a 100644 --- a/crates/hir-def/src/expr_store.rs +++ b/crates/hir-def/src/expr_store.rs @@ -795,6 +795,7 @@ impl ExpressionStore { visitor.on_pat(target); visitor.on_expr(value); } + Expr::IncludeBytes => {} } } diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs index 242a0b0b4ff9..5685c2160814 100644 --- a/crates/hir-def/src/expr_store/lower.rs +++ b/crates/hir-def/src/expr_store/lower.rs @@ -1748,6 +1748,7 @@ impl<'db> ExprCollector<'db> { self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr) } ast::Expr::FormatArgsExpr(f) => self.collect_format_args(f, syntax_ptr), + ast::Expr::IncludeBytesExpr(_) => self.alloc_expr(Expr::IncludeBytes, syntax_ptr) }) } diff --git a/crates/hir-def/src/expr_store/pretty.rs b/crates/hir-def/src/expr_store/pretty.rs index 293adfc9bd45..f52603d8e337 100644 --- a/crates/hir-def/src/expr_store/pretty.rs +++ b/crates/hir-def/src/expr_store/pretty.rs @@ -537,6 +537,7 @@ impl Printer<'_> { Expr::Missing => w!(self, "�"), Expr::Underscore => w!(self, "_"), Expr::InlineAsm(_) => w!(self, "builtin#asm(_)"), + Expr::IncludeBytes => w!(self, "include_bytes!(_)"), Expr::OffsetOf(offset_of) => { w!(self, "builtin#offset_of("); self.print_type_ref(offset_of.container); diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index 2f7724c72e22..a80056904402 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -323,6 +323,7 @@ pub enum Expr { Underscore, OffsetOf(OffsetOf), InlineAsm(InlineAsm), + IncludeBytes, } impl Expr { @@ -344,7 +345,8 @@ impl Expr { | Expr::RecordLit { .. } | Expr::Tuple { .. } | Expr::OffsetOf(_) - | Expr::Underscore => ExprPrecedence::Unambiguous, + | Expr::Underscore + | Expr::IncludeBytes => ExprPrecedence::Unambiguous, Expr::Await { .. } | Expr::Call { .. } diff --git a/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs index 9181ad88b6cc..e12cdef495a8 100644 --- a/crates/hir-expand/src/builtin/fn_macro.rs +++ b/crates/hir-expand/src/builtin/fn_macro.rs @@ -852,17 +852,9 @@ fn include_bytes_expand( span: Span, ) -> ExpandResult { // FIXME: actually read the file here if the user asked for macro expansion - let underscore = sym::underscore; - let zero = tt::Literal { - text_and_suffix: sym::_0_u8, - span, - kind: tt::LitKind::Integer, - suffix_len: 3, - }; - // We don't use a real length since we can't know the file length, so we use an underscore - // to infer it. + let pound = mk_pound(span); let res = quote! {span => - &[#zero; #underscore] + builtin #pound include_bytes }; ExpandResult::ok(res) } diff --git a/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs index 2642844e3840..025d4c138dea 100644 --- a/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs +++ b/crates/hir-ty/src/infer/closure/analysis/expr_use_visitor.rs @@ -692,6 +692,8 @@ impl<'a, 'b, 'db, D: Delegate<'db>> ExprUseVisitor<'a, 'b, 'db, D> { self.consume_expr(rhs)?; } } + + Expr::IncludeBytes => {} } Ok(()) } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 75e64403341c..4903a15a0802 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -269,7 +269,8 @@ impl<'db> InferenceContext<'_, 'db> { | Expr::Box { .. } | Expr::RecordLit { .. } | Expr::Yeet { .. } - | Expr::Missing => false, + | Expr::Missing + | Expr::IncludeBytes => false, } } @@ -893,6 +894,11 @@ impl<'db> InferenceContext<'_, 'db> { self.types.types.unit } } + Expr::IncludeBytes => { + let len = self.table.next_const_var(Span::Dummy); + let arr = Ty::new_array_with_const_len(self.interner(), self.types.types.u8, len); + Ty::new_ref(self.interner(), self.types.regions.statik, arr, Mutability::Not) + } }; let ty = self.insert_type_vars_shallow(ty); self.write_expr_ty(tgt_expr, ty); diff --git a/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs index 483f54a2270a..4fa1b71ad4a9 100644 --- a/crates/hir-ty/src/infer/mutability.rs +++ b/crates/hir-ty/src/infer/mutability.rs @@ -197,7 +197,8 @@ impl<'db> InferenceContext<'_, 'db> { | Expr::Literal(_) | Expr::Path(_) | Expr::Continue { .. } - | Expr::Underscore => (), + | Expr::Underscore + | Expr::IncludeBytes => (), } } diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 4e52c1f7c305..6499c703a81e 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1397,6 +1397,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> { Ok(Some(current)) } Expr::Underscore => Ok(Some(current)), + Expr::IncludeBytes => not_supported!("include_bytes!()"), } } diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index e30b21c139fa..15ecabc33c81 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -364,7 +364,8 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { | ast::Expr::YeetExpr(_) | ast::Expr::OffsetOfExpr(_) | ast::Expr::FormatArgsExpr(_) - | ast::Expr::AsmExpr(_) => cb(expr), + | ast::Expr::AsmExpr(_) + | ast::Expr::IncludeBytesExpr(_) => cb(expr), } } diff --git a/crates/ide-diagnostics/src/handlers/type_must_be_known.rs b/crates/ide-diagnostics/src/handlers/type_must_be_known.rs index a03352fe31d0..ae4e6c6b22b6 100644 --- a/crates/ide-diagnostics/src/handlers/type_must_be_known.rs +++ b/crates/ide-diagnostics/src/handlers/type_must_be_known.rs @@ -155,6 +155,19 @@ fn foo>(_: T) -> U { } fn bar() { let _: () = foo(any()); +} + "#, + ); + } + + #[test] + fn include_bytes() { + check_diagnostics( + r#" +//- minicore: include_bytes + +fn foo() { + include_bytes!("./foo.txt"); } "#, ); diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index cabdd74b09ce..bc7db630d13d 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -320,6 +320,10 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { // builtin#naked_asm(""); // } parse_asm_expr(p, m) + } else if p.eat_contextual_kw(T![include_bytes]) { + // test include_bytes + // fn foo() { builtin # include_bytes } + Some(m.complete(p, INCLUDE_BYTES_EXPR)) } else { m.abandon(p); None diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index dd675e083405..1c4fcf080f4a 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -125,6 +125,7 @@ pub enum SyntaxKind { FORMAT_ARGS_KW, GEN_KW, GLOBAL_ASM_KW, + INCLUDE_BYTES_KW, INLATEOUT_KW, INOUT_KW, IS_KW, @@ -225,6 +226,7 @@ pub enum SyntaxKind { IMPL, IMPL_RESTRICTION, IMPL_TRAIT_TYPE, + INCLUDE_BYTES_EXPR, INDEX_EXPR, INFER_TYPE, ITEM_LIST, @@ -412,6 +414,7 @@ impl SyntaxKind { | IMPL | IMPL_RESTRICTION | IMPL_TRAIT_TYPE + | INCLUDE_BYTES_EXPR | INDEX_EXPR | INFER_TYPE | ITEM_LIST @@ -641,6 +644,7 @@ impl SyntaxKind { DYN_KW => "dyn", FORMAT_ARGS_KW => "format_args", GLOBAL_ASM_KW => "global_asm", + INCLUDE_BYTES_KW => "include_bytes", INLATEOUT_KW => "inlateout", INOUT_KW => "inout", IS_KW => "is", @@ -750,6 +754,7 @@ impl SyntaxKind { DYN_KW if edition < Edition::Edition2018 => true, FORMAT_ARGS_KW => true, GLOBAL_ASM_KW => true, + INCLUDE_BYTES_KW => true, INLATEOUT_KW => true, INOUT_KW => true, IS_KW => true, @@ -847,6 +852,7 @@ impl SyntaxKind { DYN_KW if edition < Edition::Edition2018 => true, FORMAT_ARGS_KW => true, GLOBAL_ASM_KW => true, + INCLUDE_BYTES_KW => true, INLATEOUT_KW => true, INOUT_KW => true, IS_KW => true, @@ -1007,6 +1013,7 @@ impl SyntaxKind { "dyn" if edition < Edition::Edition2018 => DYN_KW, "format_args" => FORMAT_ARGS_KW, "global_asm" => GLOBAL_ASM_KW, + "include_bytes" => INCLUDE_BYTES_KW, "inlateout" => INLATEOUT_KW, "inout" => INOUT_KW, "is" => IS_KW, @@ -1185,6 +1192,7 @@ macro_rules ! T_ { [dyn] => { $ crate :: SyntaxKind :: DYN_KW }; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW }; [global_asm] => { $ crate :: SyntaxKind :: GLOBAL_ASM_KW }; + [include_bytes] => { $ crate :: SyntaxKind :: INCLUDE_BYTES_KW }; [inlateout] => { $ crate :: SyntaxKind :: INLATEOUT_KW }; [inout] => { $ crate :: SyntaxKind :: INOUT_KW }; [is] => { $ crate :: SyntaxKind :: IS_KW }; diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index ccf8b89be74a..26d28ddfc6ad 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -362,6 +362,8 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/impl_type_params.rs"); } #[test] + fn include_bytes() { run_and_expect_no_errors("test_data/parser/inline/ok/include_bytes.rs"); } + #[test] fn index_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/index_expr.rs"); } #[test] fn label() { run_and_expect_no_errors("test_data/parser/inline/ok/label.rs"); } diff --git a/crates/parser/test_data/parser/inline/ok/include_bytes.rast b/crates/parser/test_data/parser/inline/ok/include_bytes.rast new file mode 100644 index 000000000000..df47eb92074b --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/include_bytes.rast @@ -0,0 +1,23 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + INCLUDE_BYTES_EXPR + BUILTIN_KW "builtin" + WHITESPACE " " + POUND "#" + WHITESPACE " " + INCLUDE_BYTES_KW "include_bytes" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/include_bytes.rs b/crates/parser/test_data/parser/inline/ok/include_bytes.rs new file mode 100644 index 000000000000..6367235d72a8 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/include_bytes.rs @@ -0,0 +1 @@ +fn foo() { builtin # include_bytes } diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 408f2f4b32c7..3101a254e363 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -442,6 +442,12 @@ Expr = | YeetExpr | LetExpr | UnderscoreExpr +| IncludeBytesExpr + +// We need a special expression for this because we don't have access to the file, +// and emitting a specific array will have problems with the length. +IncludeBytesExpr = + 'builtin' '#' 'include_bytes' OffsetOfExpr = Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')' diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index c18311bfa3ad..fcdcc01f4461 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -845,6 +845,19 @@ impl ImplTraitType { #[inline] pub fn impl_token(&self) -> Option { support::token(&self.syntax, T![impl]) } } +pub struct IncludeBytesExpr { + pub(crate) syntax: SyntaxNode, +} +impl IncludeBytesExpr { + #[inline] + pub fn pound_token(&self) -> Option { support::token(&self.syntax, T![#]) } + #[inline] + pub fn builtin_token(&self) -> Option { support::token(&self.syntax, T![builtin]) } + #[inline] + pub fn include_bytes_token(&self) -> Option { + support::token(&self.syntax, T![include_bytes]) + } +} pub struct IndexExpr { pub(crate) syntax: SyntaxNode, } @@ -2212,6 +2225,7 @@ pub enum Expr { ForExpr(ForExpr), FormatArgsExpr(FormatArgsExpr), IfExpr(IfExpr), + IncludeBytesExpr(IncludeBytesExpr), IndexExpr(IndexExpr), LetExpr(LetExpr), Literal(Literal), @@ -4373,6 +4387,38 @@ impl fmt::Debug for ImplTraitType { f.debug_struct("ImplTraitType").field("syntax", &self.syntax).finish() } } +impl AstNode for IncludeBytesExpr { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + INCLUDE_BYTES_EXPR + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == INCLUDE_BYTES_EXPR } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for IncludeBytesExpr { + fn hash(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for IncludeBytesExpr {} +impl PartialEq for IncludeBytesExpr { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for IncludeBytesExpr { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for IncludeBytesExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IncludeBytesExpr").field("syntax", &self.syntax).finish() + } +} impl AstNode for IndexExpr { #[inline] fn kind() -> SyntaxKind @@ -8101,6 +8147,10 @@ impl From for Expr { #[inline] fn from(node: IfExpr) -> Expr { Expr::IfExpr(node) } } +impl From for Expr { + #[inline] + fn from(node: IncludeBytesExpr) -> Expr { Expr::IncludeBytesExpr(node) } +} impl From for Expr { #[inline] fn from(node: IndexExpr) -> Expr { Expr::IndexExpr(node) } @@ -8205,6 +8255,7 @@ impl AstNode for Expr { | FOR_EXPR | FORMAT_ARGS_EXPR | IF_EXPR + | INCLUDE_BYTES_EXPR | INDEX_EXPR | LET_EXPR | LITERAL @@ -8246,6 +8297,7 @@ impl AstNode for Expr { FOR_EXPR => Expr::ForExpr(ForExpr { syntax }), FORMAT_ARGS_EXPR => Expr::FormatArgsExpr(FormatArgsExpr { syntax }), IF_EXPR => Expr::IfExpr(IfExpr { syntax }), + INCLUDE_BYTES_EXPR => Expr::IncludeBytesExpr(IncludeBytesExpr { syntax }), INDEX_EXPR => Expr::IndexExpr(IndexExpr { syntax }), LET_EXPR => Expr::LetExpr(LetExpr { syntax }), LITERAL => Expr::Literal(Literal { syntax }), @@ -8289,6 +8341,7 @@ impl AstNode for Expr { Expr::ForExpr(it) => &it.syntax, Expr::FormatArgsExpr(it) => &it.syntax, Expr::IfExpr(it) => &it.syntax, + Expr::IncludeBytesExpr(it) => &it.syntax, Expr::IndexExpr(it) => &it.syntax, Expr::LetExpr(it) => &it.syntax, Expr::Literal(it) => &it.syntax, @@ -10405,6 +10458,11 @@ impl std::fmt::Display for ImplTraitType { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for IncludeBytesExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for IndexExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/prec.rs b/crates/syntax/src/ast/prec.rs index d99cf492616e..841127535054 100644 --- a/crates/syntax/src/ast/prec.rs +++ b/crates/syntax/src/ast/prec.rs @@ -134,7 +134,8 @@ pub fn precedence(expr: &ast::Expr) -> ExprPrecedence { | Expr::RecordExpr(_) | Expr::TupleExpr(_) | Expr::UnderscoreExpr(_) - | Expr::WhileExpr(_) => ExprPrecedence::Unambiguous, + | Expr::WhileExpr(_) + | Expr::IncludeBytesExpr(_) => ExprPrecedence::Unambiguous, } } @@ -375,7 +376,7 @@ impl Expr { ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) | BlockExpr(_) - | RecordExpr(_) | UnderscoreExpr(_) => (0, 0), + | RecordExpr(_) | UnderscoreExpr(_) | IncludeBytesExpr(_) => (0, 0), } } @@ -519,7 +520,8 @@ impl Expr { AsmExpr(e) => e.builtin_token(), ArrayExpr(_) | TupleExpr(_) | Literal(_) | PathExpr(_) | ParenExpr(_) | IfExpr(_) | WhileExpr(_) | ForExpr(_) | LoopExpr(_) | MatchExpr(_) - | BlockExpr(_) | RecordExpr(_) | UnderscoreExpr(_) | MacroExpr(_) => None, + | BlockExpr(_) | RecordExpr(_) | UnderscoreExpr(_) | MacroExpr(_) + | IncludeBytesExpr(_) => None, }; token.map(|t| t.text_range()).unwrap_or_else(|| this.syntax().text_range()).start() @@ -546,7 +548,7 @@ impl Expr { .map(|e| e.child_is_followed_by_a_block()) .unwrap_or(false), - ForExpr(_) | IfExpr(_) | MatchExpr(_) | WhileExpr(_) => true, + ForExpr(_) | IfExpr(_) | MatchExpr(_) | WhileExpr(_) | IncludeBytesExpr(_) => true, } } } diff --git a/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs index 49a625bdbd40..1ae139fe7e7b 100644 --- a/xtask/src/codegen/grammar/ast_src.rs +++ b/xtask/src/codegen/grammar/ast_src.rs @@ -154,6 +154,7 @@ const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[ "deref", "pattern_type", "is", + "include_bytes", ]; // keywords that are keywords depending on the edition