[libc-commits] [libc] [libc] implement putwc (PR #196165)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Wed May 6 12:58:19 PDT 2026
https://github.com/michaelrj-google created https://github.com/llvm/llvm-project/pull/196165
Add putwc function and tests. Part 9/11. All build file changes are in
part 11.
Assisted by Gemini
>From ddb119a79b20059d5615ad0719924ecc300e413a Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 6 May 2026 18:28:31 +0000
Subject: [PATCH] [libc] implement putwc
Add putwc function and tests. Part 9/11. All build file changes are in
part 11.
Assisted by Gemini
---
libc/src/wchar/putwc.cpp | 33 +++++++
libc/src/wchar/putwc.h | 22 +++++
libc/test/src/wchar/putwc_test.cpp | 134 +++++++++++++++++++++++++++++
3 files changed, 189 insertions(+)
create mode 100644 libc/src/wchar/putwc.cpp
create mode 100644 libc/src/wchar/putwc.h
create mode 100644 libc/test/src/wchar/putwc_test.cpp
diff --git a/libc/src/wchar/putwc.cpp b/libc/src/wchar/putwc.cpp
new file mode 100644
index 0000000000000..2e4600c9113ae
--- /dev/null
+++ b/libc/src/wchar/putwc.cpp
@@ -0,0 +1,33 @@
+//===-- Implementation of putwc -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/putwc.h"
+#include "hdr/types/FILE.h"
+#include "hdr/types/wint_t.h"
+#include "hdr/wchar_macros.h" // For WEOF
+#include "src/__support/File/file.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wint_t, putwc, (wchar_t wc, ::FILE *stream)) {
+ LIBC_CRASH_ON_NULLPTR(stream);
+ auto *f = reinterpret_cast<File *>(stream);
+ FileIOResult result = f->write(&wc, 1);
+ if (result.has_error() || result.value < 1) {
+ if (result.has_error())
+ libc_errno = result.error;
+ return WEOF;
+ }
+ return static_cast<wint_t>(wc);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/putwc.h b/libc/src/wchar/putwc.h
new file mode 100644
index 0000000000000..87b9ca5306ad4
--- /dev/null
+++ b/libc/src/wchar/putwc.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for putwc -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_PUTWC_H
+#define LLVM_LIBC_SRC_WCHAR_PUTWC_H
+
+#include "hdr/types/FILE.h"
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wint_t putwc(wchar_t wc, ::FILE *stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_PUTWC_H
diff --git a/libc/test/src/wchar/putwc_test.cpp b/libc/test/src/wchar/putwc_test.cpp
new file mode 100644
index 0000000000000..2aff8a1d17f07
--- /dev/null
+++ b/libc/test/src/wchar/putwc_test.cpp
@@ -0,0 +1,134 @@
+//===-- Unittests for putwc -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/wchar_macros.h" // For WEOF
+#include "src/stdio/fclose.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/wchar/fwide.h"
+#include "src/wchar/putwc.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcPutwcTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcPutwcTest, WriteASCII) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("putwc_ascii.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+
+ // Initial orientation should be 0
+ EXPECT_EQ(LIBC_NAMESPACE::fwide(file, 0), 0);
+
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'a', file), static_cast<wint_t>(L'a'));
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'b', file), static_cast<wint_t>(L'b'));
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'c', file), static_cast<wint_t>(L'c'));
+
+ // Orientation should now be wide
+ EXPECT_GT(LIBC_NAMESPACE::fwide(file, 0), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Read back raw bytes to verify
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ unsigned char buffer[5] = {0};
+ ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 3, file), size_t(3));
+ EXPECT_EQ(buffer[0], static_cast<unsigned char>('a'));
+ EXPECT_EQ(buffer[1], static_cast<unsigned char>('b'));
+ EXPECT_EQ(buffer[2], static_cast<unsigned char>('c'));
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcPutwcTest, WriteUtf8) {
+ auto FILENAME = libc_make_test_file_path(APPEND_LIBC_TEST("putwc_utf8.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+
+ // a -> L'a' (1-byte)
+ // ¢ -> L'¢' (2-byte)
+ // € -> L'€' (3-byte)
+ // 𐍈 -> L'𐍈' (4-byte)
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'a', file), static_cast<wint_t>(L'a'));
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'¢', file), static_cast<wint_t>(L'¢'));
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'€', file), static_cast<wint_t>(L'€'));
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'𐍈', file), static_cast<wint_t>(L'𐍈'));
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Read back raw bytes and verify strict UTF-8 sequences on disk
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ unsigned char buffer[20] = {0};
+ // Expecting 1 + 2 + 3 + 4 = 10 bytes
+ ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 10, file), size_t(10));
+
+ // a
+ EXPECT_EQ(buffer[0], static_cast<unsigned char>(0x61));
+
+ // ¢
+ EXPECT_EQ(buffer[1], static_cast<unsigned char>(0xC2));
+ EXPECT_EQ(buffer[2], static_cast<unsigned char>(0xA2));
+
+ // €
+ EXPECT_EQ(buffer[3], static_cast<unsigned char>(0xE2));
+ EXPECT_EQ(buffer[4], static_cast<unsigned char>(0x82));
+ EXPECT_EQ(buffer[5], static_cast<unsigned char>(0xAC));
+
+ // 𐍈
+ EXPECT_EQ(buffer[6], static_cast<unsigned char>(0xF0));
+ EXPECT_EQ(buffer[7], static_cast<unsigned char>(0x90));
+ EXPECT_EQ(buffer[8], static_cast<unsigned char>(0x8D));
+ EXPECT_EQ(buffer[9], static_cast<unsigned char>(0x88));
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcPutwcTest, InvalidStream) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("putwc_invalid.test"));
+
+ // Create file
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+ ASSERT_FALSE(file == nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+ // Open read-only
+ file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+ ASSERT_FALSE(file == nullptr);
+
+ ASSERT_ERRNO_SUCCESS();
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'a', file), static_cast<wint_t>(WEOF));
+ ASSERT_ERRNO_EQ(EBADF);
+ EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcPutwcTest, ByteModeFailure) {
+ auto FILENAME =
+ libc_make_test_file_path(APPEND_LIBC_TEST("putwc_bytemode.test"));
+ ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+ ASSERT_FALSE(file == nullptr);
+
+ // Orient to byte mode
+ EXPECT_LT(LIBC_NAMESPACE::fwide(file, -1), 0);
+
+ // Writing wide char should fail and set errno to EINVAL
+ EXPECT_EQ(LIBC_NAMESPACE::putwc(L'a', file), static_cast<wint_t>(WEOF));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+ ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
More information about the libc-commits
mailing list