https://gcc.gnu.org/g:ccf6cc9fcb985f535f7f50e5efe8c5c735e9fc9a
commit r17-2238-gccf6cc9fcb985f535f7f50e5efe8c5c735e9fc9a Author: Yap Zhi Heng <[email protected]> Date: Sun May 31 22:18:57 2026 +0800 gccrs: Implement C-style string literals Current behaviour is similar to normal string literals, except they are type-checked as slice types instead of array, and the compiled fat pointer has +1 to size to include the null terminator. gcc/rust/ChangeLog: * lex/rust-token.h (RS_TOKEN_LIST): Add C_STRING_LITERAL and RAW_C_STRING_LITERAL (unused for now). * lex/rust-lex.h (Lexer): Define new parse_c_string function. * lex/rust-lex.cc (Lexer::build_token): Implement lexing for C-style string literals. (Lexer::parse_c_string): Add implementation. * hir/tree/rust-hir-literal.h (HIR::Literal): Define new C_STRING LitType. * hir/rust-ast-lower-base.cc (ASTLoweringBase::lower_literal): Add new case for C_STRING. * parse/rust-parse-impl-attribute.hxx (Parser<ManagedTokenSource>::parse_attr_input): Add new case for C_STRING. * parse/rust-parse-impl-expr.hxx (Parser<ManagedTokenSource>::parse_literal_expr): Add new case for parsing C_STRING. (Parser<ManagedTokenSource>::null_denotation_not_path): Add new case for C_STRING. * ast/rust-ast.h (Token::is_string_lit): Add new case for C_STRING_LITERAL. * ast/rust-ast.cc (AttributeParser::parse_meta_item_inner): Add new case for C_STRING_LITERAL. * ast/rust-ast-collector.cc (TokenCollector::visit): Add new case for C_STRING_LITERAL. * typecheck/rust-hir-type-check-base.cc (TypeCheckBase::resolve_literal): Implement type checking for the new C_STRING type. * backend/rust-compile-expr.h (CompileExpr): Define new function compile_c_string_literal. * backend/rust-compile-expr.cc (CompileExpr::visit(LiteralExpr)): Add new case for C_STRING. (CompileExpr::compile_c_string_literal): Implement compilation of C-style string literals. Signed-off-by: Yap Zhi Heng <[email protected]> Diff: --- gcc/rust/ast/rust-ast-collector.cc | 6 ++ gcc/rust/ast/rust-ast.cc | 1 + gcc/rust/ast/rust-ast.h | 2 + gcc/rust/backend/rust-compile-expr.cc | 29 ++++++++ gcc/rust/backend/rust-compile-expr.h | 3 + gcc/rust/hir/rust-ast-lower-base.cc | 3 + gcc/rust/hir/tree/rust-hir-literal.h | 1 + gcc/rust/lex/rust-lex.cc | 80 ++++++++++++++++++++++ gcc/rust/lex/rust-lex.h | 1 + gcc/rust/lex/rust-token.h | 8 +++ gcc/rust/parse/rust-parse-impl-attribute.hxx | 3 + gcc/rust/parse/rust-parse-impl-expr.hxx | 9 +++ gcc/rust/typecheck/rust-hir-type-check-base.cc | 32 +++++++++ .../rust/compile/c_string_null_byte_check.rs | 7 ++ gcc/testsuite/rust/execute/torture/c_string.rs | 14 ++++ 15 files changed, 199 insertions(+) diff --git a/gcc/rust/ast/rust-ast-collector.cc b/gcc/rust/ast/rust-ast-collector.cc index 60be5e4475d5..f0d8957eff57 100644 --- a/gcc/rust/ast/rust-ast-collector.cc +++ b/gcc/rust/ast/rust-ast-collector.cc @@ -429,6 +429,9 @@ TokenCollector::visit (Token &tok) case RAW_STRING_LITERAL: push (Rust::Token::make_raw_string (tok.get_locus (), std::move (data))); break; + case C_STRING_LITERAL: + push (Rust::Token::make_c_string (tok.get_locus (), std::move (data))); + break; case INNER_DOC_COMMENT: push (Rust::Token::make_inner_doc_comment (tok.get_locus (), std::move (data))); @@ -866,6 +869,9 @@ TokenCollector::visit (Literal &lit, location_t locus) case Literal::LitType::RAW_STRING: push (Rust::Token::make_raw_string (locus, std::move (value))); break; + case Literal::LitType::C_STRING: + push (Rust::Token::make_c_string (locus, std::move (value))); + break; case Literal::LitType::INT: { auto val_len = value.length (); diff --git a/gcc/rust/ast/rust-ast.cc b/gcc/rust/ast/rust-ast.cc index 5880ea92fd3e..872dff315763 100644 --- a/gcc/rust/ast/rust-ast.cc +++ b/gcc/rust/ast/rust-ast.cc @@ -3620,6 +3620,7 @@ AttributeParser::parse_meta_item_inner () case BYTE_CHAR_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: case INT_LITERAL: case FLOAT_LITERAL: case TRUE_LITERAL: diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h index f6b704507f70..8afa04bebf41 100644 --- a/gcc/rust/ast/rust-ast.h +++ b/gcc/rust/ast/rust-ast.h @@ -209,6 +209,7 @@ public: case STRING_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: return true; default: return false; @@ -272,6 +273,7 @@ public: BYTE, BYTE_STRING, RAW_STRING, + C_STRING, INT, FLOAT, BOOL, diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc index 7529744d5724..45765e72d688 100644 --- a/gcc/rust/backend/rust-compile-expr.cc +++ b/gcc/rust/backend/rust-compile-expr.cc @@ -1089,6 +1089,10 @@ CompileExpr::visit (HIR::LiteralExpr &expr) case HIR::Literal::BYTE_STRING: translated = compile_byte_string_literal (expr, tyty); return; + + case HIR::Literal::C_STRING: + translated = compile_c_string_literal (expr, tyty); + return; } } @@ -1902,6 +1906,31 @@ CompileExpr::compile_byte_string_literal (const HIR::LiteralExpr &expr, return address_expression (constructed, expr.get_locus ()); } +tree +CompileExpr::compile_c_string_literal (const HIR::LiteralExpr &expr, + const TyTy::BaseType *tyty) +{ + // Copied from compile_string_literal + tree fat_pointer = TyTyResolveCompile::compile (ctx, tyty); + + rust_assert (expr.get_lit_type () == HIR::Literal::C_STRING); + const auto literal_value = expr.get_literal (); + + auto base = Backend::string_constant_expression (literal_value.as_string ()); + tree data = address_expression (base, expr.get_locus ()); + + TyTy::BaseType *usize = nullptr; + bool ok = ctx->get_tyctx ()->lookup_builtin ("usize", &usize); + rust_assert (ok); + tree type = TyTyResolveCompile::compile (ctx, usize); + + // +1 for null terminator, unlike Rust string literals. + tree size = build_int_cstu (type, literal_value.as_string ().size () + 1); + + return Backend::constructor_expression (fat_pointer, false, {data, size}, -1, + expr.get_locus ()); +} + tree CompileExpr::type_cast_expression (tree type_to_cast_to, tree expr_tree, location_t location) diff --git a/gcc/rust/backend/rust-compile-expr.h b/gcc/rust/backend/rust-compile-expr.h index 63ada9f33a69..532e140c9ccf 100644 --- a/gcc/rust/backend/rust-compile-expr.h +++ b/gcc/rust/backend/rust-compile-expr.h @@ -131,6 +131,9 @@ protected: tree compile_byte_string_literal (const HIR::LiteralExpr &expr, const TyTy::BaseType *tyty); + tree compile_c_string_literal (const HIR::LiteralExpr &expr, + const TyTy::BaseType *tyty); + tree type_cast_expression (tree type_to_cast_to, tree expr, location_t locus); tree array_value_expr (location_t expr_locus, diff --git a/gcc/rust/hir/rust-ast-lower-base.cc b/gcc/rust/hir/rust-ast-lower-base.cc index 6fc4463a9340..a1f84c9f0779 100644 --- a/gcc/rust/hir/rust-ast-lower-base.cc +++ b/gcc/rust/hir/rust-ast-lower-base.cc @@ -1018,6 +1018,9 @@ ASTLoweringBase::lower_literal (const AST::Literal &literal) case AST::Literal::LitType::RAW_STRING: type = HIR::Literal::LitType::STRING; break; + case AST::Literal::LitType::C_STRING: + type = HIR::Literal::LitType::C_STRING; + break; case AST::Literal::LitType::INT: type = HIR::Literal::LitType::INT; break; diff --git a/gcc/rust/hir/tree/rust-hir-literal.h b/gcc/rust/hir/tree/rust-hir-literal.h index b007f800fe7f..7b030340106a 100644 --- a/gcc/rust/hir/tree/rust-hir-literal.h +++ b/gcc/rust/hir/tree/rust-hir-literal.h @@ -33,6 +33,7 @@ public: STRING, BYTE, BYTE_STRING, + C_STRING, INT, FLOAT, BOOL diff --git a/gcc/rust/lex/rust-lex.cc b/gcc/rust/lex/rust-lex.cc index 4f135b19a34b..f5f2f4aa60da 100644 --- a/gcc/rust/lex/rust-lex.cc +++ b/gcc/rust/lex/rust-lex.cc @@ -1063,6 +1063,10 @@ Lexer::build_token () return parse_raw_byte_string (loc); } + // C-style strings + else if (current_char == 'c' && peek_input () == '"') + return parse_c_string (loc); + // raw identifiers and raw strings if (current_char == 'r') { @@ -1749,6 +1753,82 @@ Lexer::parse_byte_string (location_t loc) return Token::make_byte_string (loc, std::move (str)); } +// Parses a C-style string. +TokenPtr +Lexer::parse_c_string (location_t loc) +{ + skip_input (); + current_column++; + + // Mostly same code copied from parse_string... + + std::string str; + str.reserve (16); // some sensible default + + current_char = peek_input (); + + const location_t string_begin_locus = get_current_location (); + + while (current_char.value != '"' && !current_char.is_eof ()) + { + if (current_char.value == '\\') + { + int length = 1; + + auto escape_pair = parse_escape ('"'); + current_char = std::get<0> (escape_pair); + + if (current_char == Codepoint (0) && std::get<2> (escape_pair)) + length = std::get<1> (escape_pair) - 1; + else + length += std::get<1> (escape_pair); + + if (current_char != Codepoint (0) || !std::get<2> (escape_pair)) + str += current_char.as_string (); + + current_column += length; + + // FIXME: parse_escape does not update current_char correctly. + current_char = peek_input (); + continue; + } + + current_column++; + if (current_char.value == '\n') + { + current_line++; + current_column = 1; + // tell line_table that new line starts + start_line (current_line, max_column_hint); + } + + str += current_char; + skip_input (); + current_char = peek_input (); + } + + if (current_char.value == '"') + { + current_column++; + + skip_input (); + current_char = peek_input (); + } + else if (current_char.is_eof ()) + { + rust_error_at (string_begin_locus, "unended C string literal"); + return Token::make (END_OF_FILE, get_current_location ()); + } + else + { + rust_unreachable (); + } + + str.shrink_to_fit (); + + return Token::make_c_string (loc, std::move (str)); +} + // Parses a raw byte string. TokenPtr Lexer::parse_raw_byte_string (location_t loc) diff --git a/gcc/rust/lex/rust-lex.h b/gcc/rust/lex/rust-lex.h index 132005a164f1..8d65a6ddf551 100644 --- a/gcc/rust/lex/rust-lex.h +++ b/gcc/rust/lex/rust-lex.h @@ -147,6 +147,7 @@ private: TokenPtr parse_string (location_t loc); TokenPtr maybe_parse_raw_string (location_t loc); TokenPtr parse_raw_string (location_t loc, int initial_hash_count); + TokenPtr parse_c_string (location_t loc); TokenPtr parse_non_decimal_int_literals (location_t loc); TokenPtr parse_decimal_int_or_float (location_t loc); TokenPtr parse_char_or_lifetime (location_t loc); diff --git a/gcc/rust/lex/rust-token.h b/gcc/rust/lex/rust-token.h index f3e2e944100d..670affd72dca 100644 --- a/gcc/rust/lex/rust-token.h +++ b/gcc/rust/lex/rust-token.h @@ -134,6 +134,8 @@ enum PrimitiveCoreType RS_TOKEN (BYTE_STRING_LITERAL, "byte string literal") \ RS_TOKEN (RAW_STRING_LITERAL, "raw string literal") \ RS_TOKEN (BYTE_CHAR_LITERAL, "byte character literal") \ + RS_TOKEN (C_STRING_LITERAL, "C string literal") \ + RS_TOKEN (RAW_C_STRING_LITERAL, "raw C string literal") \ RS_TOKEN (LIFETIME, "lifetime") /* TODO: improve token type */ \ /* Have "interpolated" tokens (whatever that means)? identifer, path, type, \ * pattern, */ \ @@ -422,6 +424,11 @@ public: return TokenPtr (new Token (RAW_STRING_LITERAL, locus, std::move (str))); } + static TokenPtr make_c_string (location_t locus, std::string str) + { + return TokenPtr (new Token (C_STRING_LITERAL, locus, std::move (str))); + } + // Makes and returns a new TokenPtr of type INNER_DOC_COMMENT. static TokenPtr make_inner_doc_comment (location_t locus, std::string str) { @@ -504,6 +511,7 @@ public: case BYTE_CHAR_LITERAL: case BYTE_STRING_LITERAL: case RAW_STRING_LITERAL: + case C_STRING_LITERAL: return true; default: return false; diff --git a/gcc/rust/parse/rust-parse-impl-attribute.hxx b/gcc/rust/parse/rust-parse-impl-attribute.hxx index 3db1cd5c69e4..d473c9e2b9b0 100644 --- a/gcc/rust/parse/rust-parse-impl-attribute.hxx +++ b/gcc/rust/parse/rust-parse-impl-attribute.hxx @@ -336,6 +336,9 @@ Parser<ManagedTokenSource>::parse_attr_input () case BYTE_STRING_LITERAL: lit_type = AST::Literal::BYTE_STRING; break; + case C_STRING_LITERAL: + lit_type = AST::Literal::C_STRING; + break; case RAW_STRING_LITERAL: lit_type = AST::Literal::RAW_STRING; break; diff --git a/gcc/rust/parse/rust-parse-impl-expr.hxx b/gcc/rust/parse/rust-parse-impl-expr.hxx index aaffa3dcba65..cc5c40f715a3 100644 --- a/gcc/rust/parse/rust-parse-impl-expr.hxx +++ b/gcc/rust/parse/rust-parse-impl-expr.hxx @@ -342,6 +342,11 @@ Parser<ManagedTokenSource>::parse_literal_expr (AST::AttrVec outer_attrs) literal_value = t->get_str (); lexer.skip_token (); break; + case C_STRING_LITERAL: + type = AST::Literal::C_STRING; + literal_value = t->get_str (); + lexer.skip_token (); + break; case INT_LITERAL: type = AST::Literal::INT; literal_value = LiteralResolve::evaluate_integer_literal (t); @@ -2111,6 +2116,10 @@ Parser<ManagedTokenSource>::null_denotation_not_path ( return std::unique_ptr<AST::LiteralExpr> ( new AST::LiteralExpr (tok->get_str (), AST::Literal::RAW_STRING, tok->get_type_hint (), {}, tok->get_locus ())); + case C_STRING_LITERAL: + return std::unique_ptr<AST::LiteralExpr> ( + new AST::LiteralExpr (tok->get_str (), AST::Literal::C_STRING, + tok->get_type_hint (), {}, tok->get_locus ())); case CHAR_LITERAL: return std::unique_ptr<AST::LiteralExpr> ( new AST::LiteralExpr (tok->get_str (), AST::Literal::CHAR, diff --git a/gcc/rust/typecheck/rust-hir-type-check-base.cc b/gcc/rust/typecheck/rust-hir-type-check-base.cc index bacd5f5f4eb0..9cded2dbca95 100644 --- a/gcc/rust/typecheck/rust-hir-type-check-base.cc +++ b/gcc/rust/typecheck/rust-hir-type-check-base.cc @@ -397,7 +397,39 @@ TypeCheckBase::resolve_literal (const Analysis::NodeMapping &expr_mappings, TyTy::Region::make_static ()); } break; + case HIR::Literal::LitType::C_STRING: + { + // Throw error if C string literal contains null byte + if (literal.as_string ().find ('\0') != std::string::npos) + { + rust_error_at ( + locus, "null characters in C string literals are not supported"); + infered = new TyTy::ErrorType (expr_mappings.get_hirid (), locus); + break; + } + + /* This is a pointer to a null-terminated byte slice (&[u8]). */ + TyTy::BaseType *u8; + auto ok = context->lookup_builtin ("u8", &u8); + rust_assert (ok); + auto crate_num = mappings.get_current_crate (); + Analysis::NodeMapping slice_mapping (crate_num, UNKNOWN_NODEID, + mappings.get_next_hir_id ( + crate_num), + UNKNOWN_LOCAL_DEFID); + + TyTy::SliceType *slice + = new TyTy::SliceType (slice_mapping.get_hirid (), locus, + TyTy::TyVar (u8->get_ref ())); + context->insert_type (slice_mapping, slice); + + infered = new TyTy::ReferenceType (expr_mappings.get_hirid (), + TyTy::TyVar (slice->get_ref ()), + Mutability::Imm, + TyTy::Region::make_static ()); + } + break; default: rust_unreachable (); break; diff --git a/gcc/testsuite/rust/compile/c_string_null_byte_check.rs b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs new file mode 100644 index 000000000000..6d31e0a4e96e --- /dev/null +++ b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs @@ -0,0 +1,7 @@ +#![feature(no_core)] +#![no_core] + +pub fn main() { + let _fail = c"gc\0crs"; + // { dg-error "null characters in C string literals are not supported" "" { target *-*-* } .-1 } +} \ No newline at end of file diff --git a/gcc/testsuite/rust/execute/torture/c_string.rs b/gcc/testsuite/rust/execute/torture/c_string.rs new file mode 100644 index 000000000000..58b481198bfa --- /dev/null +++ b/gcc/testsuite/rust/execute/torture/c_string.rs @@ -0,0 +1,14 @@ +// { dg-output "gccrs\n" } +#![feature(no_core)] +#![no_core] + +extern "C" { + fn printf(s: *const i8, ...); +} + +pub fn main() { + let a = c"gccrs"; + unsafe { + printf(a as *const [u8] as *const i8); // TODO change *const [u8] to .as_ptr() when C strings are compiled to their own CStr type + } +}
