Skip to content

[Support] Add format object for interleaved ranges #135517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions llvm/include/llvm/Support/InterleavedRange.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===- InterleavedRange.h - Output stream formatting for ranges -----------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Implements format objects for printing ranges to output streams.
// For example:
// ```c++
// ArrayRef<Type> Types = ...;
// OS << "Types: " << interleaved(Types); // ==> "Types: i32, f16, i8"
// ArrayRef<int> Values = ...;
// OS << "Values: " << interleaved_array(Values); // ==> "Values: [1, 2, 3]"
// ```
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_SUPPORT_INTERLEAVED_RANGE_H
#define LLVM_SUPPORT_INTERLEAVED_RANGE_H

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"

namespace llvm {

/// Format object class for interleaved ranges. Supports specifying the
/// separator and, optionally, the prefix and suffix to be printed surrounding
/// the range.
/// Uses the operator '<<' of the range element type for printing. The range
/// type itself does not have to have an '<<' operator defined.
template <typename Range> class InterleavedRange {
const Range &TheRange;
StringRef Separator;
StringRef Prefix;
StringRef Suffix;

public:
InterleavedRange(const Range &R, StringRef Separator, StringRef Prefix,
StringRef Suffix)
: TheRange(R), Separator(Separator), Prefix(Prefix), Suffix(Suffix) {}

friend raw_ostream &operator<<(raw_ostream &OS,
const InterleavedRange &Interleaved) {
if (!Interleaved.Prefix.empty())
OS << Interleaved.Prefix;
llvm::interleave(Interleaved.TheRange, OS, Interleaved.Separator);
if (!Interleaved.Suffix.empty())
OS << Interleaved.Suffix;
return OS;
}

std::string str() const {
std::string Result;
raw_string_ostream Stream(Result);
Stream << *this;
Stream.flush();
return Result;
}

operator std::string() const { return str(); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question - do you want explicit here?

Copy link
Member Author

@kuhar kuhar Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm::formatv doesn't use explicit either and it seems to work fine. Do you see some footguns here? Because this is not a reference/handle/pointer type, I think it's OK.

};

/// Output range `R` as a sequence of interleaved elements. Requires the range
/// element type to be printable using `raw_ostream& operator<<`. The
/// `Separator` and `Prefix` / `Suffix` can be customized. Examples:
/// ```c++
/// SmallVector<int> Vals = {1, 2, 3};
/// OS << interleaved(Vals); // ==> "1, 2, 3"
/// OS << interleaved(Vals, ";"); // ==> "1;2;3"
/// OS << interleaved(Vals, " ", "{", "}"); // ==> "{1 2 3}"
/// ```
template <typename Range>
InterleavedRange<Range> interleaved(const Range &R, StringRef Separator = ", ",
StringRef Prefix = "",
StringRef Suffix = "") {
return {R, Separator, Prefix, Suffix};
}

/// Output range `R` as an array of interleaved elements. Requires the range
/// element type to be printable using `raw_ostream& operator<<`. The
/// `Separator` can be customized. Examples:
/// ```c++
/// SmallVector<int> Vals = {1, 2, 3};
/// OS << interleaved_array(Vals); // ==> "[1, 2, 3]"
/// OS << interleaved_array(Vals, ";"); // ==> "[1;2;3]"
/// OS << interleaved_array(Vals, " "); // ==> "[1 2 3]"
/// ```
template <typename Range>
InterleavedRange<Range> interleaved_array(const Range &R,
StringRef Separator = ", ") {
return {R, Separator, "[", "]"};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a lost cause, but I don't /think/ we use {} to invoke ctors for types that aren't pretty struct-like (exposing their members publicly? some other aspects?), I'd have expected this to be return InterleavedRange(R, Separator, ...); at least, possibly with the <Range> in there too.

But... it's probably the case that there is more {} usage than I'd expect and unlikely to be clawed back in any way, so I'm not sure of the direction I'm suggesting/wouldn't feel strongly about sticking with this code as-is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is not uncommon in llvm and I don't see anything against it in the coding standards doc, so I'm going to keep this as-is.

}

} // end namespace llvm

#endif // LLVM_SUPPORT_INTERLEAVED_RANGE_H
3 changes: 2 additions & 1 deletion llvm/unittests/Support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ add_llvm_unittest(SupportTests
HashBuilderTest.cpp
IndexedAccessorTest.cpp
InstructionCostTest.cpp
InterleavedRangeTest.cpp
JSONTest.cpp
KnownBitsTest.cpp
LEB128Test.cpp
Expand All @@ -61,7 +62,7 @@ add_llvm_unittest(SupportTests
MemoryBufferRefTest.cpp
MemoryBufferTest.cpp
MemoryTest.cpp
MustacheTest.cpp
MustacheTest.cpp
ModRefTest.cpp
NativeFormatTests.cpp
OptimizedStructLayoutTest.cpp
Expand Down
70 changes: 70 additions & 0 deletions llvm/unittests/Support/InterleavedRangeTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//===- InterleavedRangeTest.cpp - Unit tests for interleaved format -------===//
//
// 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 "llvm/Support/InterleavedRange.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

using namespace llvm;

namespace {

TEST(InterleavedRangeTest, VectorInt) {
SmallVector<int> V = {0, 1, 2, 3};

// First, make sure that the raw print API works as expected.
std::string Buff;
raw_string_ostream OS(Buff);
OS << interleaved(V);
EXPECT_EQ("0, 1, 2, 3", Buff);
Buff.clear();
OS << interleaved_array(V);
EXPECT_EQ("[0, 1, 2, 3]", Buff);

// In the rest of the tests, use `.str()` for convenience.
EXPECT_EQ("0, 1, 2, 3", interleaved(V).str());
EXPECT_EQ("{{0,1,2,3}}", interleaved(V, ",", "{{", "}}").str());
EXPECT_EQ("[0, 1, 2, 3]", interleaved_array(V).str());
EXPECT_EQ("[0;1;2;3]", interleaved_array(V, ";").str());
EXPECT_EQ("0;1;2;3", interleaved(V, ";").str());
}

TEST(InterleavedRangeTest, VectorIntEmpty) {
SmallVector<int> V = {};
EXPECT_EQ("", interleaved(V).str());
EXPECT_EQ("{{}}", interleaved(V, ",", "{{", "}}").str());
EXPECT_EQ("[]", interleaved_array(V).str());
EXPECT_EQ("", interleaved(V, ";").str());
}

TEST(InterleavedRangeTest, VectorIntOneElem) {
SmallVector<int> V = {42};
EXPECT_EQ("42", interleaved(V).str());
EXPECT_EQ("{{42}}", interleaved(V, ",", "{{", "}}").str());
EXPECT_EQ("[42]", interleaved_array(V).str());
EXPECT_EQ("42", interleaved(V, ";").str());
}

struct CustomPrint {
int N;
friend raw_ostream &operator<<(raw_ostream &OS, const CustomPrint &CP) {
OS << "$$" << CP.N << "##";
return OS;
}
};

TEST(InterleavedRangeTest, CustomPrint) {
CustomPrint V[] = {{3}, {4}, {5}};
EXPECT_EQ("$$3##, $$4##, $$5##", interleaved(V).str());
EXPECT_EQ("{{$$3##;$$4##;$$5##}}", interleaved(V, ";", "{{", "}}").str());
EXPECT_EQ("[$$3##, $$4##, $$5##]", interleaved_array(V).str());
}

} // namespace