]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
gccrs: Implement C-style string literals
authorYap Zhi Heng <yapzhhg@gmail.com>
Sun, 31 May 2026 14:18:57 +0000 (22:18 +0800)
committerArthur Cohen <arthur.cohen@embecosm.com>
Wed, 8 Jul 2026 15:22:47 +0000 (17:22 +0200)
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 <yapzhhg@gmail.com>
15 files changed:
gcc/rust/ast/rust-ast-collector.cc
gcc/rust/ast/rust-ast.cc
gcc/rust/ast/rust-ast.h
gcc/rust/backend/rust-compile-expr.cc
gcc/rust/backend/rust-compile-expr.h
gcc/rust/hir/rust-ast-lower-base.cc
gcc/rust/hir/tree/rust-hir-literal.h
gcc/rust/lex/rust-lex.cc
gcc/rust/lex/rust-lex.h
gcc/rust/lex/rust-token.h
gcc/rust/parse/rust-parse-impl-attribute.hxx
gcc/rust/parse/rust-parse-impl-expr.hxx
gcc/rust/typecheck/rust-hir-type-check-base.cc
gcc/testsuite/rust/compile/c_string_null_byte_check.rs [new file with mode: 0644]
gcc/testsuite/rust/execute/torture/c_string.rs [new file with mode: 0644]

index 60be5e4475d5dba655a8c2ee22917bec9ad7af3f..f0d8957eff575e7c0741dd460c6f7234d7278933 100644 (file)
@@ -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 ();
index 5880ea92fd3e8b926ea7a7d1dc379cd86054e195..872dff3157637e4dad608aba50156bed66774d8c 100644 (file)
@@ -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:
index f6b704507f70af804c0ab649ce8c1ca8d9a02e79..8afa04bebf41f7322e36fa62a5c35517791560d9 100644 (file)
@@ -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,
index 7529744d5724ef92048f2f4f8f6d98b1e370c99c..45765e72d688073093f5d736cfa7d71a571a7b5b 100644 (file)
@@ -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)
index 63ada9f33a6950d30c7c5f42bf8fcae5f31bcd5e..532e140c9ccf55b568c25093c02e02254f4d52f0 100644 (file)
@@ -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,
index 6fc4463a934022538b49508c560370ec7b8bca74..a1f84c9f0779f92ce33987748e671031a18cdfa0 100644 (file)
@@ -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;
index b007f800fe7f3e98cc3c3841c9f77c5e8d7a858d..7b030340106ab0a5d3ef86f6ebe7cbc8ab99ee17 100644 (file)
@@ -33,6 +33,7 @@ public:
     STRING,
     BYTE,
     BYTE_STRING,
+    C_STRING,
     INT,
     FLOAT,
     BOOL
index 4f135b19a34bdedb49f1970303120950516ddf1e..f5f2f4aa60da039863852963741f21764b028eda 100644 (file)
@@ -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)
index 132005a164f18a2a387aabbeadfe62089e380441..8d65a6ddf551f98924188171382dffe13ea15f55 100644 (file)
@@ -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);
index f3e2e944100ddd61d8fde241093bb7fd290ea2d8..670affd72dca0cf2fb49767c23515cb6c0773132 100644 (file)
@@ -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;
index 3db1cd5c69e4f94db722f0f52687aafeed392cbd..d473c9e2b9b01dba0db2af538f881e6d2223790b 100644 (file)
@@ -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;
index aaffa3dcba650bc9b3ba9349ba3e3a8ccfc5f11c..cc5c40f715a3a5583f1f4253e4cd1a873b9e0de2 100644 (file)
@@ -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,
index bacd5f5f4eb0dbc562b5552a238dfac19baa6916..9cded2dbca95ee0933d0651d73f150072d6918f8 100644 (file)
@@ -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 (file)
index 0000000..6d31e0a
--- /dev/null
@@ -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 (file)
index 0000000..58b4811
--- /dev/null
@@ -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
+    }
+}