Added error "method signature already defined in this scope"

This commit is contained in:
bigguy345 committed 2025-12-28 19:41:28 +02:00
1 parent 6d20f5f52c
commit e2a5d254a9
5 files changed
+251 -1

No files matched your search

@@ -30,6 +30,7 @@ public final class MethodInfo {
MISSING_RETURN, // Non-void method missing return statement
RETURN_TYPE_MISMATCH, // Return statement type doesn't match method return type
VOID_METHOD_RETURNS_VALUE, // Void method returns a value
DUPLICATE_METHOD, // Method with same signature already defined in scope
DUPLICATE_PARAMETER, // Two parameters have the same name
PARAMETER_UNDEFINED // Parameter type cannot be resolved
}
@@ -191,6 +192,16 @@ public final class MethodInfo {
return position >= bodyStart && position < bodyEnd;
}
/**
* Get the end of the method declaration (closing paren position).
* This is used for error highlighting of duplicate methods.
*/
public int getDeclarationEnd() {
// The declaration ends just before the opening brace
// We find the closing paren by searching backwards from bodyStart
return bodyStart > 0 ? bodyStart - 1 : nameOffset + name.length();
}
/**
* Check if this method has a parameter with the given name.
*/
@@ -215,6 +226,22 @@ public final class MethodInfo {
return null;
}
/**
* Get method signature for duplicate detection.
* Signature = name + parameter types (ignoring parameter names).
*/
public MethodSignature cachedSignature;
public MethodSignature getSignature() {
if (cachedSignature == null) {
List<TypeInfo> paramTypes = new ArrayList<>();
for (FieldInfo param : parameters)
paramTypes.add(param.getDeclaredType());
cachedSignature = new MethodSignature(name, paramTypes);
}
return cachedSignature;
}
/**
* Get the appropriate TokenType for highlighting this method.
*/
@@ -0,0 +1,79 @@
package noppes.npcs.client.gui.util.script.interpreter;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a method signature for robust comparison.
* Compares method name and parameter types as structured data, not as strings.
*/
public final class MethodSignature {
private final String methodName;
private final List<TypeInfo> parameterTypes;
public MethodSignature(String methodName, List<TypeInfo> parameterTypes) {
this.methodName = methodName;
this.parameterTypes = new ArrayList<>(parameterTypes);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MethodSignature))
return false;
MethodSignature other = (MethodSignature) obj;
// Compare method names
if (!this.methodName.equals(other.methodName))
return false;
// Compare parameter count
if (this.parameterTypes.size() != other.parameterTypes.size())
return false;
// Compare each parameter type
for (int i = 0; i < this.parameterTypes.size(); i++) {
TypeInfo thisType = this.parameterTypes.get(i);
TypeInfo otherType = other.parameterTypes.get(i);
// Both null = equal
if (thisType == null && otherType == null)
continue;
// One null, one not = not equal
if (thisType == null || otherType == null)
return false;
// Compare full type names
if (!thisType.getFullName().equals(otherType.getFullName()))
return false;
}
return true;
}
@Override
public int hashCode() {
int result = methodName.hashCode();
for (TypeInfo paramType : parameterTypes) {
result = 31 * result + (paramType != null ? paramType.getFullName().hashCode() : 0);
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(methodName);
sb.append("(");
for (int i = 0; i < parameterTypes.size(); i++) {
if (i > 0)
sb.append(", ");
TypeInfo type = parameterTypes.get(i);
sb.append(type != null ? type.getFullName() : "?");
}
sb.append(")");
return sb.toString();
}
}
@@ -1,6 +1,5 @@
package noppes.npcs.client.gui.util.script.interpreter;
import net.minecraft.client.Minecraft;
import noppes.npcs.client.ClientProxy;
import java.util.*;
@@ -604,6 +603,9 @@ public class ScriptDocument {
methods.add(methodInfo);
}
// Check for duplicate method declarations
checkDuplicateMethods();
// Also parse JS-style functions
Matcher funcM = FUNC_PARAMS_PATTERN.matcher(text);
while (funcM.find()) {
@@ -666,6 +668,97 @@ public class ScriptDocument {
return earliestPos;
}
/**
* Check for duplicate method declarations with same signature in same scope.
*/
private void checkDuplicateMethods() {
// Group methods by scope depth
Map<Integer, List<MethodInfo>> methodsByScope = new HashMap<>();
for (MethodInfo method : methods) {
int scopeDepth = calculateScopeDepth(method.getFullDeclarationOffset());
methodsByScope.computeIfAbsent(scopeDepth, k -> new ArrayList<>()).add(method);
}
// Check each scope for duplicates
for (List<MethodInfo> scopeMethods : methodsByScope.values()) {
Map<MethodSignature, List<MethodInfo>> signatureMap = new HashMap<>();
for (MethodInfo method : scopeMethods) {
MethodSignature signature = method.getSignature();
signatureMap.computeIfAbsent(signature, k -> new ArrayList<>()).add(method);
}
// Mark all methods with duplicate signatures
for (Map.Entry<MethodSignature, List<MethodInfo>> entry : signatureMap.entrySet()) {
List<MethodInfo> duplicates = entry.getValue();
if (duplicates.size() > 1) {
for (MethodInfo duplicate : duplicates) {
duplicate.setError(
MethodInfo.ErrorType.DUPLICATE_METHOD,
duplicate.getSignature() + " is already defined in this scope"
);
}
}
}
}
}
/**
* Calculate the scope depth of a position (0 = top level, 1 = in class, etc.).
*/
private int calculateScopeDepth(int position) {
int depth = 0;
int braceDepth = 0;
boolean inString = false;
boolean inComment = false;
boolean inLineComment = false;
for (int i = 0; i < position && i < text.length(); i++) {
char c = text.charAt(i);
char next = (i + 1 < text.length()) ? text.charAt(i + 1) : 0;
// Handle strings
if (c == '"' && !inComment && !inLineComment) {
inString = !inString;
continue;
}
if (inString) continue;
// Handle comments
if (!inComment && !inLineComment && c == '/' && next == '/') {
inLineComment = true;
i++;
continue;
}
if (!inComment && !inLineComment && c == '/' && next == '*') {
inComment = true;
i++;
continue;
}
if (inComment && c == '*' && next == '/') {
inComment = false;
i++;
continue;
}
if (inLineComment && c == '\n') {
inLineComment = false;
continue;
}
if (inComment || inLineComment) continue;
// Track braces
if (c == '{') {
braceDepth++;
depth = Math.max(depth, braceDepth);
} else if (c == '}') {
braceDepth--;
}
}
return depth;
}
private List<FieldInfo> parseParametersWithPositions(String paramList, int paramListStart) {
List<FieldInfo> params = new ArrayList<>();
if (paramList == null || paramList.trim().isEmpty()) {
@@ -522,6 +522,47 @@ public class ScriptLine {
}
}
// Handle duplicate method error (underline from full declaration start to closing paren)
if (method.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) {
int declStart = method.getFullDeclarationOffset();
int declEnd = method.getDeclarationEnd();
// Skip if declaration doesn't intersect this line
if (declEnd < lineStart || declStart > lineEnd)
continue;
// Clip to line boundaries
int clipStart = Math.max(declStart, lineStart);
int clipEnd = Math.min(declEnd, lineEnd);
if (clipStart >= clipEnd)
continue;
// Convert to line-local coordinates
int lineLocalStart = clipStart - lineStart;
int lineLocalEnd = clipEnd - lineStart;
// Bounds check
if (lineLocalStart < 0 || lineLocalStart >= lineText.length())
continue;
// Compute pixel position
String beforeDecl = lineText.substring(0, lineLocalStart);
int beforeWidth = ClientProxy.Font.width(beforeDecl);
int declWidth;
if (lineLocalEnd > lineText.length()) {
declWidth = ClientProxy.Font.width(lineText.substring(lineLocalStart));
} else {
String declTextOnLine = lineText.substring(lineLocalStart, lineLocalEnd);
declWidth = ClientProxy.Font.width(declTextOnLine);
}
if (declWidth > 0) {
drawCurlyUnderline(lineStartX + beforeWidth, baselineY, declWidth, 0xFF5555);
}
}
// Handle parameter errors (underline specific parameters)
if (method.hasParameterErrors()) {
for (MethodInfo.ParameterError paramError : method.getParameterErrors()) {
@@ -221,6 +221,16 @@ public class TokenHoverInfo {
}
}
// If hovering over duplicate method declaration, show the error
if (methodDecl.getErrorType() == MethodInfo.ErrorType.DUPLICATE_METHOD) {
int declStart = methodDecl.getFullDeclarationOffset();
int declEnd = methodDecl.getDeclarationEnd();
if (tokenStart >= declStart && tokenEnd <= declEnd) {
errors.add(methodDecl.getErrorMessage());
}
}
// If hovering over a parameter with an error, show that error
if (methodDecl.hasParameterErrors()) {
for (MethodInfo.ParameterError paramError : methodDecl.getParameterErrors()) {