Skip to content

feat: add strings.repeat builtin - #35

Closed
anakrish wants to merge 1 commit into
mainfrom
test-review-format
Closed

feat: add strings.repeat builtin#35
anakrish wants to merge 1 commit into
mainfrom
test-review-format

Conversation

@anakrish

Copy link
Copy Markdown
Owner

Adds a new strings.repeat(s, count) builtin that repeats a string count times.

Example:

strings.repeat("abc", 3)  # returns "abcabcabc"

This follows the pattern of other string builtins like strings.reverse and strings.count.

Adds strings.repeat(s, count) which repeats string s count times.

Example: strings.repeat("ab", 3) returns "ababab"

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new strings.repeat(s, count) builtin to the Rego string builtin set, aligning with other strings.* helpers in src/builtins/strings.rs.

Changes:

  • Register a new builtin name strings.repeat with arity 2.
  • Implement repeat builtin logic to concatenate a string count times.

Comment thread src/builtins/strings.rs
Comment on lines +605 to +607

let mut result = String::new();
for _ in 0..count {

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

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

The repeat loop grows result potentially many times without any call to enforce_limit(). Other builtins that accumulate in loops call enforce_limit() to respect the engine’s memory limits; without it, strings.repeat can more easily trigger OOM/DoS for large count. Add limit enforcement during repetition (and/or before growth) so large repeats fail gracefully under configured limits.

Suggested change
let mut result = String::new();
for _ in 0..count {
let part_len = s.len();
let mut result = String::new();
for _ in 0..count {
let next_len = match result.len().checked_add(part_len) {
Some(next_len) => next_len,
None => bail!(params[1].span().error("repeat result exceeds supported size")),
};
enforce_limit(next_len)?;

Copilot uses AI. Check for mistakes.
Comment thread src/builtins/strings.rs
Comment on lines +605 to +606

let mut result = String::new();

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

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

result is built with String::new() and repeated push_str, which can lead to repeated reallocations for larger counts. Consider reserving capacity up front using a checked length computation (e.g., checked_mul of s.len() and count) or otherwise minimizing reallocations, while ensuring the approach can’t panic on overflow.

Suggested change
let mut result = String::new();
let capacity = s
.len()
.checked_mul(count)
.ok_or_else(|| params[1].span().error("repeat count produces a string that is too large"))?;
let mut result = String::new();
result
.try_reserve(capacity)
.map_err(|_| params[1].span().error("repeat count produces a string that is too large"))?;

Copilot uses AI. Check for mistakes.
Comment thread src/builtins/strings.rs
Comment on lines +595 to +612
fn repeat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "strings.repeat";
ensure_args_count(span, name, params, args, 2)?;

let s = ensure_string(name, &params[0], &args[0])?;
let count = args[1].as_f64().unwrap() as usize;

if count == 0 {
return Ok(Value::String("".into()));
}

let mut result = String::new();
for _ in 0..count {
result.push_str(&s);
}

Ok(Value::String(result.into()))
}

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

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

A new builtin (strings.repeat) is being added but there are no corresponding interpreter YAML test cases under tests/interpreter/cases/builtins/strings/ (this directory already contains coverage for other string builtins like concat, contains, etc.). Add tests for basic behavior (e.g., count 0/1/N), Unicode handling, and invalid count cases (non-integer / negative) to lock down semantics across execution paths.

Copilot uses AI. Check for mistakes.
Comment thread src/builtins/strings.rs
m.insert("strings.any_suffix_match", (any_suffix_match, 2));
m.insert("strings.count", (strings_count, 2));
m.insert("strings.replace_n", (replace_n, 2));
m.insert("strings.repeat", (repeat, 2));

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

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

strings.repeat is now registered but the builtin list in docs/builtins.md has a dedicated Strings table that enumerates supported builtins (e.g., strings.replace_n, strings.reverse). Add strings.repeat there as well so the documented builtin surface matches what the engine exposes.

Copilot uses AI. Check for mistakes.
Comment thread src/builtins/strings.rs
Comment on lines +595 to +600
fn repeat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "strings.repeat";
ensure_args_count(span, name, params, args, 2)?;

let s = ensure_string(name, &params[0], &args[0])?;
let count = args[1].as_f64().unwrap() as usize;

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

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

args[1].as_f64().unwrap() as usize can panic (unwrap), silently truncates non-integers, and will produce a huge usize for negative counts due to casting. This violates the project’s no-panics requirement and yields incorrect semantics for invalid count values. Parse count via ensure_numeric and validate it as a non-negative integer (e.g., using validate_integer_arg), then convert using an explicit checked/option-based conversion (returning Undefined or a strict-mode error as appropriate).

Suggested change
fn repeat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "strings.repeat";
ensure_args_count(span, name, params, args, 2)?;
let s = ensure_string(name, &params[0], &args[0])?;
let count = args[1].as_f64().unwrap() as usize;
fn repeat(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "strings.repeat";
ensure_args_count(span, name, params, args, 2)?;
let s = ensure_string(name, &params[0], &args[0])?;
let count = ensure_numeric(name, &params[1], &args[1])?;
let count = match count.as_i64().and_then(|value| usize::try_from(value).ok()) {
Some(count) => count,
None if strict => {
bail!(params[1]
.span()
.error("`strings.repeat` expects a non-negative integer count"))
}
None => return Ok(Value::Undefined),
};

Copilot uses AI. Check for mistakes.
@anakrish anakrish closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants