Skip to content

Commit e045739

Browse files
authored
Merge pull request #2317 from GaijinEntertainment/string-lint-daslib
Fix perf_lint warnings across daslib, modules, tutorials
2 parents f7b5aa6 + 41d904e commit e045739

20 files changed

+335
-300
lines changed

daslib/constant_expression.das

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,12 @@ class ConstantExpressionMacro : AstFunctionAnnotation {
100100
return <- default<ExpressionPtr>
101101
}
102102
var inscope func_copy <- clone_function(expr.func)
103-
var func_name = "{func_copy.name}`constant_expression"
104-
for (i in argi) {
105-
let fhash = hash(describe(expr.arguments[i]))
106-
func_name += "`{fhash}"
103+
var func_name = build_string() <| $(var w) {
104+
w |> write("{func_copy.name}`constant_expression")
105+
for (i in argi) {
106+
let fhash = hash(describe(expr.arguments[i]))
107+
w |> write("`{fhash}")
108+
}
107109
}
108110
func_copy.name := func_name
109111
if (expr.func.fromGeneric != null) {

daslib/coverage.das

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require rtti
2424
require strings
2525

2626
require daslib/ast_boost
27+
require daslib/strings_boost
2728
require daslib/templates_boost
2829

2930

@@ -120,12 +121,11 @@ def private single_file_report(name : string) {
120121
def get_report(name : string = "") : string {
121122
//! Returns code coverage report in lcov format for the specified file, or all files if name is empty.
122123
if (name |> empty) {
123-
var res = ""
124-
125-
for (k in keys(coverageData)) {
126-
res += single_file_report(k)
124+
return build_string() <| $(var w) {
125+
for (k in keys(coverageData)) {
126+
w |> write(single_file_report(k))
127+
}
127128
}
128-
return res
129129
} else {
130130
return single_file_report(name)
131131
}

daslib/interfaces.das

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module interfaces shared private
2727

2828
require daslib/ast_boost
2929
require daslib/templates_boost
30+
require daslib/strings_boost
3031
require daslib/defer
3132
require daslib/generic_return
3233
require strings
@@ -275,31 +276,32 @@ class ImplementsMacro : AstStructureAnnotation {
275276
return true // apply already reported this error
276277
}
277278
// Check each interface method
278-
var missing : string
279-
for (ifld in iface.fields) {
280-
let ifld_name = string(ifld.name)
281-
// Skip compiler-generated fields
282-
if (ifld_name == "__rtti" || ifld_name == "__finalize") {
283-
continue
284-
}
285-
if (!ifld._type.isFunction) {
286-
continue
287-
}
288-
// Methods with a default implementation (init != null) are optional
289-
if (ifld.init != null) {
290-
continue
291-
}
292-
// Abstract method — struct must provide {iface_name}`{method_name}
293-
let expected = "{iface_name}`{ifld_name}"
294-
var found = false
295-
for (sfld in st.fields) {
296-
if (string(sfld.name) == expected) {
297-
found = true
298-
break
279+
var missing = build_string() <| $(var w) {
280+
for (ifld in iface.fields) {
281+
let ifld_name = string(ifld.name)
282+
// Skip compiler-generated fields
283+
if (ifld_name == "__rtti" || ifld_name == "__finalize") {
284+
continue
285+
}
286+
if (!ifld._type.isFunction) {
287+
continue
288+
}
289+
// Methods with a default implementation (init != null) are optional
290+
if (ifld.init != null) {
291+
continue
292+
}
293+
// Abstract method — struct must provide {iface_name}`{method_name}
294+
let expected = "{iface_name}`{ifld_name}"
295+
var found = false
296+
for (sfld in st.fields) {
297+
if (string(sfld.name) == expected) {
298+
found = true
299+
break
300+
}
301+
}
302+
if (!found) {
303+
w |> write("{st.name} does not implement {iface_name}.{ifld_name}\n")
299304
}
300-
}
301-
if (!found) {
302-
missing = "{missing}{st.name} does not implement {iface_name}.{ifld_name}\n"
303305
}
304306
}
305307
if (length(missing) > 0) {

daslib/json_boost.das

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ def public parse_json_annotation(name : string; annotation : array<tuple<name :
439439
if (ann.name == "rename") {
440440
if (ann.data is tString) {
441441
fieldState.argName = ann.data as tString
442-
} elif (ann.data is tBool && length(name) > 0 && character_at(name, 0) == '_') {
442+
} elif (ann.data is tBool && length(name) > 0 && first_character(name) == '_') {
443443
fieldState.argName = slice(name, 1)
444444
}
445445
} elif (ann.name == "enum_as_int" && ann.data is tBool) {

daslib/perf_lint.das

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ class PerfLintVisitor : AstVisitor {
9696

9797
// --- expression helpers ---
9898

99+
def is_character_at_zero(expr : smart_ptr<ExprCall>) : bool {
100+
if (length(expr.arguments) >= 2) {
101+
var idx = get_ptr(expr.arguments[1])
102+
if (idx is ExprConstInt && (idx as ExprConstInt).value == 0) {
103+
return true
104+
}
105+
}
106+
return false
107+
}
108+
99109
def find_string_var_from_expr(expr : Expression?) : Variable? {
100110
return null if (expr == null)
101111
// Unwrap ExprRef2Value (compiler inserts these for value-type reads)
@@ -231,18 +241,23 @@ class PerfLintVisitor : AstVisitor {
231241
}
232242

233243
def override visitExprCall(var expr : smart_ptr<ExprCall>) : ExpressionPtr {
234-
return <- expr if (in_closure > 0)
244+
// PERF003: character_at anywhere — fires even inside closures
235245
if (expr.func.name == "character_at" && expr.func._module.name == "strings") {
236246
var ecall = get_ptr(expr)
237-
if (loop_depth > 0 && in_character_at_call > 0) {
247+
if (in_closure == 0 && loop_depth > 0 && in_character_at_call > 0) {
238248
in_character_at_call--
239249
current_character_at = null
240250
}
241-
// PERF003: character_at anywhere (info) — deferred so PERF002 can claim it first
251+
// deferred so PERF002 can claim it first
242252
if (!reported_character_at |> has_value(ecall)) {
243-
self->perf_warning("PERF003: character_at is O(n) due to strlen; consider peek_data() for hot paths", expr.at)
253+
if (self->is_character_at_zero(expr)) {
254+
self->perf_warning("PERF003: character_at(s, 0) can be replaced with first_character(s)", expr.at)
255+
} else {
256+
self->perf_warning("PERF003: character_at is O(n) due to strlen; consider peek_data() for hot paths", expr.at)
257+
}
244258
}
245259
}
260+
return <- expr if (in_closure > 0)
246261
if (expr.func.name == "length" && expr.func._module.name == "strings") {
247262
if (in_length_while_call > 0) {
248263
in_length_while_call--

daslib/regex.das

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,9 +1707,9 @@ def private re_match2_repeat_lazy(var regex : Regex; var node : ReNode?; str : u
17071707

17081708
def private re_early_out(var cset : CharSet; node : ReNode?) : bool {
17091709
if (node.op == ReOp.Char) {
1710-
set_or_char(cset, character_at(node.text, 0))
1710+
let fc = first_character(node.text)
1711+
set_or_char(cset, fc)
17111712
// if case-insensitive, also add the opposite-case first char
1712-
let fc = character_at(node.text, 0)
17131713
if (fc >= 'A' && fc <= 'Z') {
17141714
set_or_char(cset, fc + 32)
17151715
} elif (fc >= 'a' && fc <= 'z') {
@@ -1953,53 +1953,58 @@ def private expand_replacement(var regex : Regex; str : string; replacement : st
19531953
//! Expands replacement template with group references: $0 or $& for whole match,
19541954
//! $1-$9 for numbered groups, ${name} for named groups, $$ for literal $.
19551955
return build_string() $(writer) {
1956-
var i = 0
19571956
let rlen = length(replacement)
1958-
while (i < rlen) {
1959-
let ch = character_at(replacement, i)
1960-
if (ch == '$' && i + 1 < rlen) {
1961-
let nch = character_at(replacement, i + 1)
1962-
if (nch == '$') {
1963-
writer |> write_char('$')
1964-
i += 2
1965-
} elif (nch == '&' || nch == '0') {
1966-
writer |> write(slice(str, match_start, match_end))
1967-
i += 2
1968-
} elif (nch >= '1' && nch <= '9') {
1969-
let group_num = nch - '0'
1970-
if (group_num < length(regex.groups)) {
1971-
let grng = regex.groups[group_num]._0
1972-
writer |> write(slice(str, grng.x, grng.y))
1973-
}
1974-
i += 2
1975-
} elif (nch == '{') {
1976-
let close = find(replacement, "}", i + 2)
1977-
if (close != -1) {
1978-
let name = slice(replacement, i + 2, close)
1979-
// try numeric group reference first (${0}, ${1}, ...)
1980-
var is_numeric = !empty(name)
1981-
for (nc in name) {
1982-
if (nc < '0' || nc > '9') {
1983-
is_numeric = false
1984-
break
1985-
}
1957+
peek_data(replacement) $(rdata) {
1958+
var i = 0
1959+
while (i < rlen) {
1960+
let ch = int(rdata[i])
1961+
if (ch == '$' && i + 1 < rlen) {
1962+
let nch = int(rdata[i + 1])
1963+
if (nch == '$') {
1964+
writer |> write_char('$')
1965+
i += 2
1966+
} elif (nch == '&' || nch == '0') {
1967+
writer |> write(slice(str, match_start, match_end))
1968+
i += 2
1969+
} elif (nch >= '1' && nch <= '9') {
1970+
let group_num = nch - '0'
1971+
if (group_num < length(regex.groups)) {
1972+
let grng = regex.groups[group_num]._0
1973+
writer |> write(slice(str, grng.x, grng.y))
19861974
}
1987-
if (is_numeric) {
1988-
var group_num = 0
1975+
i += 2
1976+
} elif (nch == '{') {
1977+
let close = find(replacement, "}", i + 2)
1978+
if (close != -1) {
1979+
let name = slice(replacement, i + 2, close)
1980+
// try numeric group reference first (${0}, ${1}, ...)
1981+
var is_numeric = !empty(name)
19891982
for (nc in name) {
1990-
group_num = group_num * 10 + (nc - '0')
1983+
if (nc < '0' || nc > '9') {
1984+
is_numeric = false
1985+
break
1986+
}
19911987
}
1992-
if (group_num == 0) {
1993-
writer |> write(slice(str, match_start, match_end))
1994-
} elif (group_num < length(regex.groups)) {
1995-
let grng = regex.groups[group_num]._0
1996-
writer |> write(slice(str, grng.x, grng.y))
1988+
if (is_numeric) {
1989+
var group_num = 0
1990+
for (nc in name) {
1991+
group_num = group_num * 10 + (nc - '0')
1992+
}
1993+
if (group_num == 0) {
1994+
writer |> write(slice(str, match_start, match_end))
1995+
} elif (group_num < length(regex.groups)) {
1996+
let grng = regex.groups[group_num]._0
1997+
writer |> write(slice(str, grng.x, grng.y))
1998+
}
1999+
} else {
2000+
let grp = regex_group_by_name(regex, name, str)
2001+
writer |> write(grp)
19972002
}
2003+
i = close + 1
19982004
} else {
1999-
let grp = regex_group_by_name(regex, name, str)
2000-
writer |> write(grp)
2005+
writer |> write_char(ch)
2006+
i++
20012007
}
2002-
i = close + 1
20032008
} else {
20042009
writer |> write_char(ch)
20052010
i++
@@ -2008,9 +2013,6 @@ def private expand_replacement(var regex : Regex; str : string; replacement : st
20082013
writer |> write_char(ch)
20092014
i++
20102015
}
2011-
} else {
2012-
writer |> write_char(ch)
2013-
i++
20142016
}
20152017
}
20162018
}

0 commit comments

Comments
 (0)