[gcc r17-2238] gccrs: Implement C-style string literals

Arthur Cohen via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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
+    }
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.