Skip to content

Use "slices.Equal()" instead of custom implementation of this function#100

Open
paulnegz wants to merge 1 commit intoSonarSource:masterfrom
paulnegz:feature/slice-equality-check
Open

Use "slices.Equal()" instead of custom implementation of this function#100
paulnegz wants to merge 1 commit intoSonarSource:masterfrom
paulnegz:feature/slice-equality-check

Conversation

@paulnegz
Copy link
Copy Markdown

This commit introduces a new check to detect manually implemented slice equality functions and suggest using the standard slices.Equal() function instead.

The SliceEqualSimplificationCheck identifies functions that have a specific structure:

  • Two slice parameters
  • A length comparison
  • A for-loop for element-wise comparison
  • A final return true statement

This helps improve code consistency and maintainability by encouraging the use of the standard library. Comprehensive tests have been added to cover various positive and negative cases to ensure the check is accurate.

Part of

This commit introduces a new check to detect manually implemented slice equality functions and suggest using the standard `slices.Equal()` function instead.

The `SliceEqualSimplificationCheck` identifies functions that have a specific structure:
- Two slice parameters
- A length comparison
- A for-loop for element-wise comparison
- A final `return true` statement

This helps improve code consistency and maintainability by encouraging the use of the standard library. Comprehensive tests have been added to cover various positive and negative cases to ensure the check is accurate.
@mstachniuk mstachniuk changed the title feat(checks): Add SliceEqualSimplificationCheck Use "slices.Equal()" instead of custom implementation of this function Sep 3, 2025
@@ -0,0 +1,192 @@
/*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please move this file to: sonar-go-checks/src/main/java/org/sonar/go/checks/SliceEqualSimplificationCheck.java

@@ -0,0 +1,27 @@
/*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please move this file to: sonar-go-checks/src/test/java/org/sonar/go/checks/SliceEqualSimplificationCheckTest.java

@@ -0,0 +1,100 @@
// Positive cases
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please move this file to sonar-go-checks/src/test/resources/checks/SliceEqualSimplification/SliceEqualSimplification.go
(There is go extension instead of slang). Now in sonar-go we are not using *.slang files anymore but Go code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add package SliceEqualSimplification at the beginning of the file. Otherwise, the file will be not parsed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please use Sensitive cases instead of Positive cases

import org.sonarsource.slang.checks.api.SlangCheck;

@Rule(key = "S-slice-equal")
public class SliceEqualSimplificationCheck implements SlangCheck {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
public class SliceEqualSimplificationCheck implements SlangCheck {
public class SliceEqualSimplificationCheck implements GoCheck {

Comment on lines +62 to +69
// Semantic checks would be needed here to verify types.
// For example:
// if (!isSlice(param1.type()) || !isSlice(param2.type())) {
// return false;
// }
// if (!areElementTypesComparableAndIdentical(param1.type(), param2.type())) {
// return false;
// }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For now it can be approximated with:

  private static boolean areParametersSlices(ParameterTree param1, ParameterTree param2) {
    return param1.type().type().equals(param2.type().type());
  }

However it requires more complex logic, to cover some cases:

  • func equalSlicesWithDifferentVarNames(slice1, slice2 []int) bool - first type is skipped
  • func Equal[S ~[]E, E comparable](s1, s2 S) bool - original signature from Go standard library
  • func (rec *MyStruct) equalSlicesReceiver(slice1, slice2 []int) bool - what if there is receiver?
  • func equalSlicesFirstParamAsPointer(s1 *[]int, s2 []int) bool - what if there is a pointer?
  • maybe some other possible cases?

Please try to cover them.

Comment on lines +175 to +191
private static boolean isReturnFalse(Tree statement) {
if (!(statement instanceof ReturnTree)) {
return false;
}
ReturnTree returnTree = (ReturnTree) statement;
Tree body = returnTree.body();
return body instanceof IdentifierTree && "false".equals(((IdentifierTree) body).name());
}

private static boolean isReturnTrue(Tree statement) {
if (!(statement instanceof ReturnTree)) {
return false;
}
ReturnTree returnTree = (ReturnTree) statement;
Tree body = returnTree.body();
return body instanceof IdentifierTree && "true".equals(((IdentifierTree) body).name());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
private static boolean isReturnFalse(Tree statement) {
if (!(statement instanceof ReturnTree)) {
return false;
}
ReturnTree returnTree = (ReturnTree) statement;
Tree body = returnTree.body();
return body instanceof IdentifierTree && "false".equals(((IdentifierTree) body).name());
}
private static boolean isReturnTrue(Tree statement) {
if (!(statement instanceof ReturnTree)) {
return false;
}
ReturnTree returnTree = (ReturnTree) statement;
Tree body = returnTree.body();
return body instanceof IdentifierTree && "true".equals(((IdentifierTree) body).name());
}
private static boolean isReturnFalse(Tree statement) {
return isReturn(statement, "false");
}
private static boolean isReturnTrue(Tree statement) {
return isReturn(statement, "true");
}
private static boolean isReturn(Tree statement, String returnValue) {
if (statement instanceof ReturnTree returnTree &&
returnTree.expressions().size() == 1) {
Tree body = returnTree.expressions().get(0);
return body instanceof LiteralTree LiteralTreeBody && returnValue.equals(LiteralTreeBody.value());
}
return false;
}


@Test
void test() {
Verifier.verify("SliceEqualSimplification.slang", new SliceEqualSimplificationCheck());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Verifier.verify("SliceEqualSimplification.slang", new SliceEqualSimplificationCheck());
GoVerifier.verify("SliceEqualSimplification/SliceEqualSimplification.go", new SliceEqualSimplificationCheck());

@@ -0,0 +1,100 @@
// Positive cases
fun equalSlices(a, b) { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add:

//   ^^^^^^^^^^^

in next line, to indicate that the issue should be only raised on the method name.

}
return true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add following cases:

// function with generics like in slices.Equal()
func Equal[S ~[]E, E comparable](s1, s2 S) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
	if len(s1) != len(s2) {
		return false
	}
	for i := range s1 {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

func (rec *MyStruct) equalSlicesReceiver(slice1, slice2 []int) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
	if len(slice1) != len(slice2) {
		return false
	}
	for i := range slice1 {
		if slice1[i] != slice2[i] {
			return false
		}
	}
	return true
}

func equalSlicesFirstParamAsPointer(s1 *[]int, s2 []int) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
	if len(*s1) != len(s2) {
		return false
	}
	for i := range *s1 {
		if (*s1)[i] != s2[i] {
			return false
		}
	}
	return true
}

And maybe you can think up more cases.

}

// Negative cases - should not be flagged
fun notEqualSlicesWrongLenCheck(a, b) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please change this code to Go code.

@mstachniuk
Copy link
Copy Markdown
Contributor

Hi @paulnegz !
Sorry for late review, I was off for few weeks and busy with other activities.
I wrote a lot of comments, please address them. Please ask if something is unclear.
Please be sure that you can run locally the SliceEqualSimplificationCheckTest (it is currently not the case).
After addressing the comments I will do a second round of the review, maybe someone else from my team will also look on it.
Please do not treat my comments as push back. We care about the quality of the code we deliver.
I'm happy to continue to work with you on your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants