Skip to content

Commit d01a92e

Browse files
Fixed issue in len with string concatenation in argument
1 parent 8a27348 commit d01a92e

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

compiler/compiler_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,50 @@ func TestDeclSelection_RemoveUnusedTypeConstraint(t *testing.T) {
360360
sel.InitCode.IsDead(`ghost = new Bar\[\d+ /\* int \*/\]\.ptr\(7\)`)
361361
}
362362

363+
func TestLengthParenthesizingIssue841(t *testing.T) {
364+
// See issue https://github.com/gopherjs/gopherjs/issues/841
365+
//
366+
// Summary: Given `len(a+b)` where a and b are strings being concatenated
367+
// together, the result was `a + b.length` instead of `(a+b).length`.
368+
//
369+
// The fix was to check if the expression in `len` contains a binary
370+
// expression or not. If it does, then the expression is parenthesized.
371+
// This will work for concatenations any combination of variables and literals.
372+
373+
src := `
374+
package main
375+
376+
func main() {
377+
a := "a"
378+
b := "b"
379+
ab := a + b
380+
if len(a+b) != len(ab) {
381+
panic("unreachable")
382+
}
383+
}`
384+
385+
srcFiles := []srctesting.Source{{Name: `main.go`, Contents: []byte(src)}}
386+
root := srctesting.ParseSources(t, srcFiles, nil)
387+
archives := compileProject(t, root, false)
388+
mainPkg := archives[root.PkgPath]
389+
390+
badRegex := regexp.MustCompile(`a\s*\+\s*b\.length`)
391+
goodRegex := regexp.MustCompile(`\(a\s*\+\s*b\)\.length`)
392+
goodFound := false
393+
for i, decl := range mainPkg.Declarations {
394+
if badRegex.Match(decl.DeclCode) {
395+
t.Errorf("found length issue in decl #%d: %s", i, decl.FullName)
396+
t.Logf("decl code:\n%s", string(decl.DeclCode))
397+
}
398+
if goodRegex.Match(decl.DeclCode) {
399+
goodFound = true
400+
}
401+
}
402+
if !goodFound {
403+
t.Error("parenthesized length not found")
404+
}
405+
}
406+
363407
func compareOrder(t *testing.T, sourceFiles []srctesting.Source, minify bool) {
364408
t.Helper()
365409
outputNormal := compile(t, sourceFiles, minify)

compiler/expressions.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,9 @@ func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args
982982
case "len":
983983
switch argType := fc.typeOf(args[0]).Underlying().(type) {
984984
case *types.Basic:
985+
if containsBinaryExpr(args[0]) {
986+
return fc.formatExpr("(%e).length", args[0])
987+
}
985988
return fc.formatExpr("%e.length", args[0])
986989
case *types.Slice:
987990
return fc.formatExpr("%e.$length", args[0])

compiler/utils.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,20 @@ func isPkgLevel(o types.Object) bool {
396396
return (o.Parent() != nil && o.Parent().Parent() == types.Universe) || isType
397397
}
398398

399+
// containsBinaryExpr returns true if the given expression contains a binary
400+
// expression somewhere in its subtree.
401+
func containsBinaryExpr(expr ast.Expr) bool {
402+
var found bool
403+
ast.Inspect(expr, func(n ast.Node) bool {
404+
if _, ok := n.(*ast.BinaryExpr); ok {
405+
found = true
406+
return false
407+
}
408+
return true
409+
})
410+
return found
411+
}
412+
399413
// assignedObjectName checks if the object has been previously assigned a name
400414
// in this or one of the parent contexts. If not, found will be false.
401415
func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bool) {

0 commit comments

Comments
 (0)