From f9f7be07fb3cce11762c5f66eed96938c5f2b8d1 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Tue, 6 May 2025 16:58:46 +1000 Subject: [PATCH 1/8] Implement best-effort resolution of arguments to unresolved invocables --- CHANGELOG.md | 4 +- .../checks/VariableInitializationCheck.java | 27 +++++-- .../VariableInitializationCheckTest.java | 26 +++++-- .../symbol/resolve/InvocationArgument.java | 14 ++-- .../delphi/symbol/resolve/NameResolver.java | 70 +++++++++++++------ .../DelphiSymbolTableExecutorTest.java | 6 ++ .../delphi/symbol/BestEffortArguments.pas | 14 ++++ 7 files changed, 122 insertions(+), 39 deletions(-) create mode 100644 delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas diff --git a/CHANGELOG.md b/CHANGELOG.md index adf0564ab..3c26963c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Detect tab-indented multiline strings in `TabulationCharacter`. - Improve support for evaluating name references in compiler directive expressions. - Improve overload resolution in cases involving generic type parameter constraints. -- Improve handling for MSBuild properties, items, and conditional evaluation. +- Improve handling for MSBuild properties, items, and conditional evaluation. +- Perform best-effort name resolution of arguments to unresolved routines or array properties, which + improves analysis quality in cases where symbol information is incomplete. ### Deprecated diff --git a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/VariableInitializationCheck.java b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/VariableInitializationCheck.java index fbd6b3127..372b5bb8a 100644 --- a/delphi-checks/src/main/java/au/com/integradev/delphi/checks/VariableInitializationCheck.java +++ b/delphi-checks/src/main/java/au/com/integradev/delphi/checks/VariableInitializationCheck.java @@ -194,7 +194,7 @@ private void handleNestedRoutineInvocations(StatementNode statement, DelphiCheck private void searchForInitializationsByOutArgument(StatementNode statement) { findNameReferences(statement).stream() - .filter(VariableInitializationCheck::isOutArgument) + .filter(VariableInitializationCheck::isPotentiallyInitializingArgument) .map(this::getReferredInitializationState) .filter(Objects::nonNull) .forEach(InitializationState::assignedTo); @@ -275,7 +275,7 @@ private void handleAssignment(StatementNode statement) { } for (NameReferenceNode name : findNameReferences(assignmentStatement.getValue())) { - if (name.getNameDeclaration() == declaration && !isOutArgument(name)) { + if (name.getNameDeclaration() == declaration && !isPotentiallyInitializingArgument(name)) { return; } } @@ -294,7 +294,7 @@ private void handleAssignment(StatementNode statement) { } } - private static boolean isOutArgument(NameReferenceNode name) { + private static boolean isPotentiallyInitializingArgument(NameReferenceNode name) { ArgumentNode argument = getArgument(name); if (argument == null) { return false; @@ -303,7 +303,10 @@ private static boolean isOutArgument(NameReferenceNode name) { ArgumentListNode argumentList = (ArgumentListNode) argument.getParent(); ProceduralType procedural = getInvokedProcedural(argumentList); if (procedural == null) { - return false; + // If we can't find the procedural, it might mean: + // - an unresolved procedural reference (potentially initializing) + // - an explicit array constructor (not initializing) + return !isExplicitArrayConstructor(argumentList); } int argumentIndex = argumentList.getArgumentNodes().indexOf(argument); @@ -400,6 +403,22 @@ private static ProceduralType getInvokedProcedural(ArgumentListNode argumentList return (ProceduralType) type; } + private static boolean isExplicitArrayConstructor(ArgumentListNode argumentList) { + Node previous = argumentList.getParent().getChild(argumentList.getChildIndex() - 1); + if (previous instanceof NameReferenceNode) { + NameReferenceNode name = ((NameReferenceNode) previous).getLastName(); + NameReferenceNode prevName = name.prevName(); + if (prevName != null) { + NameDeclaration arrayDeclaration = prevName.getNameDeclaration(); + if (arrayDeclaration instanceof TypeNameDeclaration) { + return ((TypeNameDeclaration) arrayDeclaration).getType().isDynamicArray() + && name.simpleName().equalsIgnoreCase("Create"); + } + } + } + return false; + } + private static RoutineNameDeclaration getRoutineDeclaration(ArgumentListNode argumentList) { Node previous = argumentList.getParent().getChild(argumentList.getChildIndex() - 1); if (!(previous instanceof NameReferenceNode)) { diff --git a/delphi-checks/src/test/java/au/com/integradev/delphi/checks/VariableInitializationCheckTest.java b/delphi-checks/src/test/java/au/com/integradev/delphi/checks/VariableInitializationCheckTest.java index 9310b2999..9db1d4631 100644 --- a/delphi-checks/src/test/java/au/com/integradev/delphi/checks/VariableInitializationCheckTest.java +++ b/delphi-checks/src/test/java/au/com/integradev/delphi/checks/VariableInitializationCheckTest.java @@ -720,22 +720,22 @@ void testPassedToProcVarOutParameterShouldNotAddIssue() { } @Test - void testFailOnUpgradeVarPassedToArrayProcVarOutParameterShouldAddIssue() { + void testFailOnUpgradePassedToArrayElementProceduralShouldNotAddIssue() { CheckVerifier.newVerifier() .withCheck(new VariableInitializationCheck()) .onFile( new DelphiTestUnitBuilder() .appendDecl("type") - .appendDecl(" TTestProc = reference to procedure(out Int: Integer);") + .appendDecl(" TTestProc = reference to procedure(Int: Integer);") .appendDecl("procedure Foo(Int: Integer);") .appendImpl("procedure Test(ProcArray: array of TTestProc);") .appendImpl("var") .appendImpl(" I: Integer;") .appendImpl("begin") - .appendImpl(" ProcArray[0](I); // Noncompliant") + .appendImpl(" ProcArray[0](I);") .appendImpl(" Foo(I);") .appendImpl("end;")) - .verifyIssues(); + .verifyNoIssues(); } @Test @@ -947,6 +947,24 @@ void testUnknownRoutineArgumentShouldNotAddIssue() { .verifyNoIssues(); } + @Test + void testUninitializedArgumentToExplicitArrayConstructorShouldAddIssue() { + CheckVerifier.newVerifier() + .withCheck(new VariableInitializationCheck()) + .onFile( + new DelphiTestUnitBuilder() + .appendImpl("procedure Test;") + .appendImpl("type") + .appendImpl(" TBar = array of Integer;") + .appendImpl("var") + .appendImpl(" Foo: Integer;") + .appendImpl(" Bar: TBar;") + .appendImpl("begin") + .appendImpl(" Bar := TBar.Create(Foo); // Noncompliant") + .appendImpl("end;")) + .verifyIssues(); + } + @Test void testUninitializedObjectWithFreeAndNilShouldAddIssue() { CheckVerifier.newVerifier() diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationArgument.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationArgument.java index 27d2655de..f530e11d1 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationArgument.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/InvocationArgument.java @@ -29,6 +29,7 @@ import org.sonar.plugins.communitydelphi.api.ast.utils.ExpressionNodeUtils; import org.sonar.plugins.communitydelphi.api.type.Type; import org.sonar.plugins.communitydelphi.api.type.Type.ProceduralType; +import org.sonar.plugins.communitydelphi.api.type.TypeFactory; import org.sonar.plugins.communitydelphi.api.type.Typed; public class InvocationArgument implements Typed { @@ -55,7 +56,7 @@ public class InvocationArgument implements Typed { void resolve(Type parameterType) { if (resolver != null) { if (isRoutineReference(parameterType)) { - disambiguateRoutineReference(resolver, parameterType); + resolver.disambiguateRoutineReference((ProceduralType) parameterType); } else if (!resolver.isExplicitInvocation()) { resolver.disambiguateImplicitEmptyArgumentList(); } @@ -92,13 +93,12 @@ Type findRoutineReferenceType(Type parameterType) { Preconditions.checkNotNull(resolver); NameResolver clone = new NameResolver(resolver); - disambiguateRoutineReference(clone, parameterType); - return clone.getApproximateType(); - } + clone.disambiguateRoutineReference((ProceduralType) parameterType); + if (!clone.isAmbiguous()) { + return clone.getApproximateType(); + } - private static void disambiguateRoutineReference(NameResolver resolver, Type parameterType) { - resolver.disambiguateRoutineReference((ProceduralType) parameterType); - resolver.checkAmbiguity(); + return TypeFactory.unknownType(); } @Override diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index e88b9fcfa..cf64d8187 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -25,7 +25,6 @@ import static org.sonar.plugins.communitydelphi.api.type.TypeFactory.voidType; import au.com.integradev.delphi.antlr.ast.node.ArrayAccessorNodeImpl; -import au.com.integradev.delphi.antlr.ast.node.DelphiNodeImpl; import au.com.integradev.delphi.antlr.ast.node.NameReferenceNodeImpl; import au.com.integradev.delphi.symbol.Search; import au.com.integradev.delphi.symbol.SearchMode; @@ -200,8 +199,12 @@ NameDeclaration addResolvedDeclaration() { return resolved; } - public void checkAmbiguity() { - if (declarations.size() > 1) { + public boolean isAmbiguous() { + return declarations.size() > 1; + } + + private void checkAmbiguity() { + if (isAmbiguous()) { if (LOG.isWarnEnabled()) { LOG.warn( "Ambiguous declarations could not be resolved\n[Occurrence] {}\n{}", @@ -288,19 +291,11 @@ void readPrimaryExpression(PrimaryExpressionNode node) { } for (DelphiNode child : node.getChildren()) { - if (!readPrimaryExpressionPart(child)) { - break; - } + readPrimaryExpressionPart(child); } } - /** - * Reads part of a primary expression - * - * @param node part of a primary expression - * @return false if a name resolution failure occurs - */ - private boolean readPrimaryExpressionPart(Node node) { + private void readPrimaryExpressionPart(Node node) { if (node instanceof NameReferenceNode) { readNameReference((NameReferenceNode) node); } else if (node instanceof ArgumentListNode) { @@ -314,11 +309,13 @@ private boolean readPrimaryExpressionPart(Node node) { } else { handlePrimaryExpressionToken(node); } - - return !nameResolutionFailed(); } private void handlePrimaryExpressionToken(Node node) { + if (nameResolutionFailed()) { + return; + } + switch (node.getTokenType()) { case DEREFERENCE: Type dereferenced = TypeUtils.dereference(getApproximateType()); @@ -389,9 +386,7 @@ private boolean handleInheritedExpression(PrimaryExpressionNode node) { int nextChild = ExpressionNodeUtils.isBareInherited(node) ? 1 : 2; for (int i = nextChild; i < node.getChildren().size(); ++i) { - if (!readPrimaryExpressionPart(node.getChild(i))) { - break; - } + readPrimaryExpressionPart(node.getChild(i)); } return true; @@ -520,8 +515,7 @@ private void handleTypeParameterReferences(List typeReference occurrence.setNameDeclaration(declaration); ((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence); - NameResolver resolver = - new NameResolver(((DelphiNodeImpl) typeReference).getTypeFactory(), searchMode); + NameResolver resolver = new NameResolver(typeFactory, searchMode); resolver.resolvedDeclarations.add(declaration); resolver.names.add(occurrence); resolver.addToSymbolTable(); @@ -540,8 +534,7 @@ private void handleTypeParameterForwardReferences( occurrence.setNameDeclaration(declaration); ((NameReferenceNodeImpl) typeReference).setNameOccurrence(occurrence); - NameResolver resolver = - new NameResolver(((DelphiNodeImpl) typeNode).getTypeFactory(), searchMode); + NameResolver resolver = new NameResolver(typeFactory, searchMode); resolver.resolvedDeclarations.add(declaration); resolver.names.add(occurrence); resolver.addToSymbolTable(); @@ -576,6 +569,10 @@ void readAttribute(AttributeNode node) { } private void readNameReference(NameReferenceNode node, boolean inAttribute) { + if (nameResolutionFailed()) { + return; + } + boolean couldBeUnitNameReference = currentScope == null || (!(currentScope instanceof UnknownScope) && currentScope.equals(node.getScope())); @@ -709,7 +706,7 @@ private boolean isTypeParameterConstructorInvocation(ArgumentListNode argumentLi } private void readPossibleUnitNameReference(NameReferenceNode node, boolean inAttribute) { - NameResolver unitNameResolver = new NameResolver(((DelphiNodeImpl) node).getTypeFactory()); + NameResolver unitNameResolver = new NameResolver(typeFactory); if (unitNameResolver.readUnitNameReference(node, inAttribute)) { this.currentType = unknownType(); this.names.clear(); @@ -781,6 +778,11 @@ private boolean matchReferenceToUnitNameDeclaration( } private void handleArrayAccessor(ArrayAccessorNode accessor) { + if (nameResolutionFailed()) { + resolveArgumentsBestEffort(accessor.getExpressions()); + return; + } + Type type = TypeUtils.findBaseType(getApproximateType()); if (type.isPointer()) { Type dereferenced = TypeUtils.dereference(type); @@ -921,6 +923,14 @@ private void handleArgumentList(ArgumentListNode node) { } } + if (nameResolutionFailed()) { + resolveArgumentsBestEffort( + node.getArgumentNodes().stream() + .map(ArgumentNode::getExpression) + .collect(Collectors.toUnmodifiableList())); + return; + } + if (handleExplicitArrayConstructorInvocation(node) || isTypeParameterConstructorInvocation(node)) { return; @@ -1008,6 +1018,20 @@ private boolean handleProcVarInvocation(List argumentExpressions return true; } + private void resolveArgumentsBestEffort(List argumentExpressions) { + for (ExpressionNode expression : argumentExpressions) { + getNameResolutionHelper().resolveSubExpressions(expression); + if (expression instanceof PrimaryExpressionNode) { + NameResolver resolver = new NameResolver(typeFactory, searchMode); + resolver.readPrimaryExpression((PrimaryExpressionNode) expression); + if (resolver.isAmbiguous()) { + resolver.declarations.clear(); + } + resolver.addToSymbolTable(); + } + } + } + private void disambiguateArguments(List argumentExpressions, boolean explicit) { if (handleHardTypeCast(argumentExpressions) || handleProcVarInvocation(argumentExpressions)) { return; diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index 607460d51..1e7fd067b 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -426,6 +426,12 @@ void testLabels() { verifyUsages(29, 6, reference(31, 7), reference(32, 2)); } + @Test + void testBestEffortArguments() { + execute("BestEffortArguments.pas"); + verifyUsages(9, 2, reference(11, 6), reference(11, 11)); + } + @Test void testClassReferenceMethodResolution() { execute("classReferences/MethodResolution.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas new file mode 100644 index 000000000..0213d6985 --- /dev/null +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas @@ -0,0 +1,14 @@ +unit BestEffortArguments; + +interface + +implementation + +procedure Foo; +var + Bar: Integer; +begin + Baz(Bar)[Bar]; +end; + +end. \ No newline at end of file From 302da3f252303f3e3ec595118bfff80313956f80 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 11:06:56 +1000 Subject: [PATCH 2/8] Add debug logging for name resolution failures --- CHANGELOG.md | 1 + .../delphi/symbol/resolve/NameResolver.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c26963c8..0a574e371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for MSBuild item and item metadata expressions in project files. +- Debug logging for name resolution failures (enabled with `sonar-scanner -X`). - `ExhaustiveEnumCase` analysis rule, which flags `case` statements that do not handle all values in an enumeration. - `IterationPastHighBound` analysis rule, which flags `for` loops that iterate past the end of the collection. - `ExplicitBitwiseNot` analysis rule, which flags potentially incorrect bitwise `not` operations. diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index cf64d8187..237303103 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -217,6 +217,18 @@ private void checkAmbiguity() { } } + private void checkNameResolutionFailed() { + if (LOG.isDebugEnabled() && nameResolutionFailed()) { + Node location = Iterables.getLast(names).getLocation(); + LOG.debug( + "Name resolution failed on symbol '{}' ({} line {}:{})", + location.getImage(), + location.getUnitName(), + location.getBeginLine(), + location.getBeginColumn()); + } + } + void updateType(Type type) { currentType = type; ScopedType scopedType = extractScopedType(currentType); @@ -233,6 +245,7 @@ public boolean isExplicitInvocation() { public void addToSymbolTable() { addResolvedDeclaration(); + checkNameResolutionFailed(); for (int i = 0; i < resolvedDeclarations.size(); ++i) { NameOccurrenceImpl name = names.get(i); From 6c1c429b869bd0579bc3af5799cb8e40748386e5 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 14:11:34 +1000 Subject: [PATCH 3/8] Prevent double-resolution of property specifiers Property specifiers `read`/`write`/`implements`/`stored` were being resolved twice, which was unnecessary because they should be resolved as part of the top-level resolution of the `PropertyNode`. In a funny twist, it turns out we were actually relying on that second visit to resolve non-invocable expressions in `read`/`write`/`stored` specifiers. This is now handled properly in the top-level resolve. --- .../ast/visitors/SymbolTableVisitor.java | 28 +++++++++++++++++++ .../symbol/resolve/NameResolutionHelper.java | 16 +++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java index 2fc9636a3..8ffcf535b 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/antlr/ast/visitors/SymbolTableVisitor.java @@ -102,7 +102,11 @@ import org.sonar.plugins.communitydelphi.api.ast.PointerTypeNode; import org.sonar.plugins.communitydelphi.api.ast.PrimaryExpressionNode; import org.sonar.plugins.communitydelphi.api.ast.ProcedureTypeHeadingNode; +import org.sonar.plugins.communitydelphi.api.ast.PropertyImplementsSpecifierNode; import org.sonar.plugins.communitydelphi.api.ast.PropertyNode; +import org.sonar.plugins.communitydelphi.api.ast.PropertyReadSpecifierNode; +import org.sonar.plugins.communitydelphi.api.ast.PropertyStoredSpecifierNode; +import org.sonar.plugins.communitydelphi.api.ast.PropertyWriteSpecifierNode; import org.sonar.plugins.communitydelphi.api.ast.RecordTypeNode; import org.sonar.plugins.communitydelphi.api.ast.RecordVariantTagNode; import org.sonar.plugins.communitydelphi.api.ast.RepeatStatementNode; @@ -517,6 +521,30 @@ public Data visit(PropertyNode node, Data data) { return createDeclarationScope(node, data); } + @Override + public Data visit(PropertyReadSpecifierNode node, Data data) { + // Already resolved by the PropertyNode visit. + return data; + } + + @Override + public Data visit(PropertyWriteSpecifierNode node, Data data) { + // Already resolved by the PropertyNode visit. + return data; + } + + @Override + public Data visit(PropertyImplementsSpecifierNode node, Data data) { + // Already resolved by the PropertyNode visit. + return data; + } + + @Override + public Data visit(PropertyStoredSpecifierNode node, Data data) { + // Already resolved by the PropertyNode visit. + return data; + } + @Nullable private static PropertyNameDeclaration findConcretePropertyDeclaration(PropertyNode property) { if (!property.getType().isUnknown()) { diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java index b5cc8f995..9ea3151e6 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java @@ -255,7 +255,9 @@ public void resolve(PropertyNode property) { if (read != null) { NameResolver readResolver = createNameResolver(); readResolver.readPrimaryExpression(read.getExpression()); - readResolver.disambiguateParameters(getGetterParameterTypes(property)); + if (isResolvingRoutine(readResolver)) { + readResolver.disambiguateParameters(getGetterParameterTypes(property)); + } readResolver.addToSymbolTable(); } @@ -263,7 +265,9 @@ public void resolve(PropertyNode property) { if (write != null) { NameResolver writeResolver = createNameResolver(); writeResolver.readPrimaryExpression(write.getExpression()); - writeResolver.disambiguateParameters(getSetterParameterTypes(property)); + if (isResolvingRoutine(writeResolver)) { + writeResolver.disambiguateParameters(getSetterParameterTypes(property)); + } writeResolver.addToSymbolTable(); } @@ -276,11 +280,17 @@ public void resolve(PropertyNode property) { if (stored != null && stored.getExpression() instanceof PrimaryExpressionNode) { NameResolver storedResolver = createNameResolver(); storedResolver.readPrimaryExpression((PrimaryExpressionNode) stored.getExpression()); - storedResolver.disambiguateParameters(getStorageParameterTypes(property)); + if (isResolvingRoutine(storedResolver)) { + storedResolver.disambiguateParameters(getStorageParameterTypes(property)); + } storedResolver.addToSymbolTable(); } } + private static boolean isResolvingRoutine(NameResolver resolver) { + return resolver.getDeclarations().stream().anyMatch(RoutineNameDeclaration.class::isInstance); + } + private List getGetterParameterTypes(PropertyNode property) { if (property.getIndexSpecifier() == null) { return property.getParameterTypes(); From 4acc2979d8a5290d479d48cbb23b300eed53dd86 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 15:51:32 +1000 Subject: [PATCH 4/8] Prevent name resolution failure logging on implementation-local routines --- .../delphi/symbol/resolve/NameResolutionHelper.java | 7 +++++++ .../integradev/delphi/symbol/resolve/NameResolver.java | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java index 9ea3151e6..5e9c3d459 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolutionHelper.java @@ -339,6 +339,13 @@ public void resolve(RoutineImplementationNode routine) { } resolver.disambiguateIsClassInvocable(routine.isClassMethod()); + + if (resolver.nameResolutionFailed() && !routine.getNameReferenceNode().isQualified()) { + // No interface declaration found, and not a qualified name so it can't be a method. + // It must be an implementation-local routine. + return; + } + resolver.addToSymbolTable(); completeTypeParameterReferences(routine); diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index 237303103..ebb4d89ce 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -203,6 +203,10 @@ public boolean isAmbiguous() { return declarations.size() > 1; } + public boolean nameResolutionFailed() { + return names.size() != resolvedDeclarations.size() + Math.min(1, declarations.size()); + } + private void checkAmbiguity() { if (isAmbiguous()) { if (LOG.isWarnEnabled()) { @@ -664,10 +668,6 @@ private NameOccurrence createNameOccurrence(NameReferenceNode reference, boolean return occurrence; } - private boolean nameResolutionFailed() { - return names.size() != resolvedDeclarations.size() + Math.min(1, declarations.size()); - } - private void specializeDeclarations(NameOccurrence occurrence) { declarations = declarations.stream() From de49a1162c3dd7def3a538851477be2f32b6dae6 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 15:54:46 +1000 Subject: [PATCH 5/8] Fix various name resolution failures in `DelphiSymbolTableExecutorTest` These failures weren't important to the tests, but it's best for this code to be as well-formed as possible. Plus, it helped in identifying cases where we were logging name resolution failures erroneously. --- .../DelphiSymbolTableExecutorTest.java | 28 +++++++++++-------- .../symbol/BareInterfaceMethodReference.pas | 2 +- .../delphi/symbol/BestEffortArguments.pas | 2 +- .../delphi/symbol/SelfInNestedProcedures.pas | 3 ++ .../classReferences/ArgumentResolution.pas | 8 +++--- .../delphi/symbol/dependencies/Helper.pas | 7 +---- .../UnitWithGetEnumeratorForTObject.pas | 2 +- .../symbol/generics/ParameterizedMethod.pas | 8 +----- .../operators/UnaryOperatorOverloads.pas | 8 +++--- .../delphi/symbol/overloads/CharInSet.pas | 2 +- .../properties/HiddenDefaultProperties.pas | 2 +- .../delphi/symbol/typeResolution/Chars.pas | 6 ++-- .../delphi/symbol/typeResolution/Pointers.pas | 2 -- 13 files changed, 38 insertions(+), 42 deletions(-) diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index 1e7fd067b..9c884c1f4 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -325,8 +325,8 @@ void testSelfTypes() { @Test void testSelfInNestedProcedures() { execute("SelfInNestedProcedures.pas"); - verifyUsages(17, 11, reference(34, 2)); - verifyUsages(19, 11, reference(29, 4), reference(38, 4)); + verifyUsages(17, 11, reference(37, 2)); + verifyUsages(19, 11, reference(32, 4), reference(41, 4)); } @Test @@ -499,8 +499,8 @@ void testSimpleTypeResolution() { @Test void testCharTypeResolution() { execute("typeResolution/Chars.pas"); - verifyUsages(7, 9, reference(22, 2)); - verifyUsages(12, 9, reference(23, 2)); + verifyUsages(7, 10, reference(22, 2)); + verifyUsages(12, 10, reference(23, 2)); } @Test @@ -1073,10 +1073,10 @@ void testGenericParameterizedMethods() { 11, 14, reference(16, 15), - reference(29, 2), - reference(30, 2), - reference(31, 2), - reference(32, 2)); + reference(23, 2), + reference(24, 2), + reference(25, 2), + reference(26, 2)); } @Test @@ -1606,15 +1606,21 @@ private static Path createStandardLibrary(Path baseDir) { "unit System;\n" + "interface\n" + "type\n" + + " TArray = array of T;\n" + " TObject = class\n" - + " constructor Create;" + + " constructor Create;\n" + + " procedure Free;\n" + " end;\n" + " IInterface = interface\n" + " end;\n" - + " TClassHelperBase = class\n" + + " TInterfacedObject = class(TObject, IInterface)\n" + + " end;\n" + + " TClassHelperBase = class(TInterfacedObject, IInterface)\n" + " end;\n" + " TVarRec = record\n" + " end;\n" + + " TCustomAttribute = class(TObject)\n" + + " end;\n" + "implementation\n" + "end."); @@ -1716,7 +1722,7 @@ private static Path createStandardLibrary(Path baseDir) { + "function MessageDlg(const Msg: string; DlgType: TMsgDlgType;\n" + " Buttons: TMsgDlgButtons; HelpCtx: Longint): Integer;\n" + "begin\n" - + " Result := MessageDlgPosHelp(Msg, DlgType, Buttons, HelpCtx, -1, -1, '');\n" + + " // ...\n" + "end;\n" + "\n" + "end."); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BareInterfaceMethodReference.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BareInterfaceMethodReference.pas index aa1568015..c0f1e3310 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BareInterfaceMethodReference.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BareInterfaceMethodReference.pas @@ -3,7 +3,7 @@ interface function Foo(Baz: Integer): Integer; -function ExternalFunc(Result: Booelan): Boolean; +function ExternalFunc(Result: Boolean): Boolean; implementation diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas index 0213d6985..7ae11b723 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/BestEffortArguments.pas @@ -8,7 +8,7 @@ procedure Foo; var Bar: Integer; begin - Baz(Bar)[Bar]; + Baz(Bar)[Bar]; // The Baz invocation does not resolve, but the Bar expressions still should. end; end. \ No newline at end of file diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/SelfInNestedProcedures.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/SelfInNestedProcedures.pas index 662f4262a..35b8bcfdc 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/SelfInNestedProcedures.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/SelfInNestedProcedures.pas @@ -22,6 +22,9 @@ implementation uses System.SysUtils; +type + TProc = reference to procedure(Arg: T); + procedure TFoo.MyProc; procedure SubProc(Self: TBaz); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/classReferences/ArgumentResolution.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/classReferences/ArgumentResolution.pas index f3dd8dbd0..d3b9afeb8 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/classReferences/ArgumentResolution.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/classReferences/ArgumentResolution.pas @@ -15,15 +15,15 @@ TMetaFoo = class of TFoo; implementation -procedure CallBaz(Meta: TMetaFoo); +procedure AcceptFooType(Meta: TMetaFoo); begin - Baz.Baz; + // ... end; procedure Test; begin - CallBaz(TFoo); - CallBaz(TBar); + AcceptFooType(TFoo); + AcceptFooType(TBar); end; diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/Helper.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/Helper.pas index b987bbf2f..21f9d89a1 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/Helper.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/Helper.pas @@ -7,12 +7,7 @@ implementation uses System.SysUtils; -function Foo: Boolean; -begin - Result := HelperDependency.Bar.NONEXISTENT; -end; - -function Bar: Boolean; +function IsStringEmpty: Boolean; begin Result := ''.IsEmpty; end; diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/imports/UnitWithGetEnumeratorForTObject.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/imports/UnitWithGetEnumeratorForTObject.pas index f185a0b68..5f9f93f52 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/imports/UnitWithGetEnumeratorForTObject.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/dependencies/imports/UnitWithGetEnumeratorForTObject.pas @@ -6,7 +6,7 @@ interface TObjectHelper = class helper for TObject function GetEnumerator: TObject; function MoveNext: Boolean; - property Current: TObject read Foo; + property Current: TObject; end; implementation diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/generics/ParameterizedMethod.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/generics/ParameterizedMethod.pas index cf7ee72ab..7de1473d3 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/generics/ParameterizedMethod.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/generics/ParameterizedMethod.pas @@ -14,14 +14,8 @@ TFoo = class implementation procedure TFoo.CompareAndPrintResult(X, Y: T); -var - Comparer : IComparer; begin - Comparer := TComparer.Default; - if Comparer.Compare(X, Y) = 0 then - WriteLn('Both members compare as equal') - else - WriteLn('Members do not compare as equal'); + // ... end; procedure TFoo.Test; diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/operators/UnaryOperatorOverloads.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/operators/UnaryOperatorOverloads.pas index b0931dbad..a471e7e9d 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/operators/UnaryOperatorOverloads.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/operators/UnaryOperatorOverloads.pas @@ -19,14 +19,14 @@ implementation Result := True; end; -class operator TFoo.Positive(Foo: TFoo): String; +class operator TFoo.Positive(Foo: TFoo): Integer; begin - Result := ''; + Result := 123; end; -class operator TFoo.Negative(Foo: TFoo): Integer; +class operator TFoo.Negative(Foo: TFoo): String; begin - Result := 123; + Result := ''; end; procedure ExpectBoolean(Bool: Boolean); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/overloads/CharInSet.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/overloads/CharInSet.pas index 15f0ab788..aaa2f7aad 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/overloads/CharInSet.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/overloads/CharInSet.pas @@ -10,7 +10,7 @@ TFoo = class(TObject) protected FChar: Char; public - property CharProperty: Char read FToken; + property CharProperty: Char read FChar; function Test(TokenChar: TCharSet): Boolean; end; diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/HiddenDefaultProperties.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/HiddenDefaultProperties.pas index fdc1d7f27..1d18a8182 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/HiddenDefaultProperties.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/properties/HiddenDefaultProperties.pas @@ -4,7 +4,7 @@ interface type TBaseBar = class(TObject) - property DefaultProperty[AIndex: Integer]: TBar; default; + property DefaultProperty[AIndex: Integer]: TBaseBar; default; end; TBar = class(TBaseBar) diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Chars.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Chars.pas index 17230ca00..6a2050ba5 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Chars.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Chars.pas @@ -4,17 +4,17 @@ interface implementation -function Foo(Bar: AnsiChar); +procedure Foo(Bar: AnsiChar); begin // Do nothing end; -function Foo(Bar: WideChar); +procedure Foo(Bar: WideChar); begin // Do nothing end; -function TFoo.GetBar: TBar; +procedure Bar; var NarrowString: AnsiString; RegularString: String; diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Pointers.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Pointers.pas index 6fc022456..a3fa9f1fa 100644 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Pointers.pas +++ b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/typeResolution/Pointers.pas @@ -17,8 +17,6 @@ procedure Test(); Foo(@TObject.Create); Foo(0); // Literal 0 will implicitly convert to nil Foo($0); // Also applies to hexadecimal literals - Foo(123); // Does not apply to any non-0 literal - Foo(C_Zero); // Does not apply to constant values end; end. From e83bde679043bf5346cc61677d23d86c6fbcf693 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 15:58:13 +1000 Subject: [PATCH 6/8] Remove `DelphiSymbolTableExecutorTest::testSimple` test This test was low-value and full of name resolution failures. The testing surface for simple symbol resolution is well-covered by the other 148 tests in this suite. --- .../DelphiSymbolTableExecutorTest.java | 10 ---- .../com/integradev/delphi/symbol/Simple.pas | 52 ------------------- 2 files changed, 62 deletions(-) delete mode 100644 delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Simple.pas diff --git a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java index 9c884c1f4..a521bdda2 100644 --- a/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java +++ b/delphi-frontend/src/test/java/au/com/integradev/delphi/executor/DelphiSymbolTableExecutorTest.java @@ -83,16 +83,6 @@ void setup(@TempDir Path tempDir) { standardLibraryPath = createStandardLibrary(tempDir); } - @Test - void testSimpleFile() { - execute("Simple.pas"); - verifyUsages(7, 2, reference(20, 10), reference(29, 10), reference(34, 10)); - verifyUsages(12, 3); - verifyUsages(20, 2); - verifyUsages(9, 14, reference(29, 22), reference(36, 1)); - verifyUsages(10, 14, reference(31, 1), reference(34, 22)); - } - @Test void testSimilarParameterDeclarations() { execute("SimilarParameterDeclarations.pas"); diff --git a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Simple.pas b/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Simple.pas deleted file mode 100644 index 909e8de7f..000000000 --- a/delphi-frontend/src/test/resources/au/com/integradev/delphi/symbol/Simple.pas +++ /dev/null @@ -1,52 +0,0 @@ -unit Simple; - -interface - -{** documented class **} -type - TMainWindow = class(TForm) - public - procedure foo1; - procedure foo2; - private - field1: integer; - protected - { comment line } - //another comment - (* block comment *) - end; - -var - window: TMainWindow; - -implementation - -uses - OverloadTest, FunctionTest; - -{$R *.dfm} - -procedure TMainWindow.foo1; -begin - foo2; -end; - -procedure TMainWindow.foo2; -begin - foo1; - globalProcedure(); - x := globalFunction; - TOverloadTest.over1(5); - fooZZ; - bar; - getField; - fooXX; - getPrivateField(); - TFunctionTest.setField(5); - TFunctionTest.getField; - TFunctionTest.foo; - TFunctionTest.bar(); - rfcFunction(123); -end; - -end. \ No newline at end of file From 7d864ac4bf90714a6bbc4adbdc512e39052bc6a0 Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 18:33:27 +1000 Subject: [PATCH 7/8] Prevent name resolution failure logging on bare inherited expressions Delphi allows unresolved bare inherited expressions, and even generates them in event handlers. Logging them would be useless noise. --- .../com/integradev/delphi/symbol/resolve/NameResolver.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java index ebb4d89ce..15ac7a180 100644 --- a/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java +++ b/delphi-frontend/src/main/java/au/com/integradev/delphi/symbol/resolve/NameResolver.java @@ -374,6 +374,11 @@ private boolean handleInheritedExpression(PrimaryExpressionNode node) { disambiguateVisibility(); disambiguateParameters(routine.getParameterTypes()); addResolvedDeclaration(); + if (nameResolutionFailed()) { + // We don't want to log name resolution failures for unresolved bare inherited expressions, + // as Delphi allows them (and even generates them in event handlers). + names.clear(); + } } else { NameReferenceNode routineName = (NameReferenceNode) node.getChild(1); NameOccurrenceImpl occurrence = new NameOccurrenceImpl(routineName.getIdentifier()); From e34166fe29f5080ce2e0f27ebc77084a9202965f Mon Sep 17 00:00:00 2001 From: Jonah Jeleniewski Date: Wed, 7 May 2025 19:23:12 +1000 Subject: [PATCH 8/8] Fix various name resolution failures in our standard library stubs --- .../checks/verifier/CheckVerifierImpl.java | 94 ++++++++++++++++++- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java b/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java index 17a3bad93..3a02f3f63 100644 --- a/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java +++ b/delphi-checks-testkit/src/main/java/au/com/integradev/delphi/checks/verifier/CheckVerifierImpl.java @@ -557,9 +557,54 @@ private Path createStandardLibrary() { + "\n" + "interface\n" + "\n" + + "const\n" + + " vtInteger = 0;\n" + + " vtBoolean = 1;\n" + + " vtChar = 2;\n" + + " vtExtended = 3;\n" + + " vtString = 4;\n" + + " vtPointer = 5;\n" + + " vtPChar = 6;\n" + + " vtObject = 7;\n" + + " vtClass = 8;\n" + + " vtWideChar = 9;\n" + + " vtPWideChar = 10;\n" + + " vtAnsiString = 11;\n" + + " vtCurrency = 12;\n" + + " vtVariant = 13;\n" + + " vtInterface = 14;\n" + + " vtWideString = 15;\n" + + " vtInt64 = 16;\n" + + " vtUnicodeString = 17;\n" + + "\n" + "type\n" + + " Int8 = ShortInt;\n" + + " Int16 = SmallInt;\n" + + " Int32 = Integer;\n" + + " IntPtr = NativeInt;\n" + + " UInt8 = Byte;\n" + + " UInt16 = Word;\n" + + " UInt32 = Cardinal;\n" + + " UIntPtr = NativeUInt;\n" + + " Float32 = Single;\n" + + " Float64 = Double;\n" + + "\n" + + " HRESULT = type Int32;\n" + + "\n" + " TArray = array of T;\n" + "\n" + + " PGUID = ^TGUID;\n" + + " TGUID = record\n" + + " end;\n" + + "\n" + + " PInterfaceEntry = ^TInterfaceEntry;\n" + + " TInterfaceEntry = packed record\n" + + " end;\n" + + "\n" + + " PInterfaceTable = ^TInterfaceTable;\n" + + " TInterfaceTable = packed record\n" + + " end;\n" + + "\n" + " TObject = class;\n" + "\n" + " TClass = class of TObject;\n" @@ -589,11 +634,11 @@ private Path createStandardLibrary() { + " class function ClassInfo: Pointer; inline;\n" + " class function InstanceSize: Longint; inline;\n" + " class function InheritsFrom(AClass: TClass): Boolean;\n" - + " class function MethodAddress(const Name: _ShortStr): Pointer; overload;\n" + + " class function MethodAddress(const Name: ShortString): Pointer; overload;\n" + " class function MethodAddress(const Name: string): Pointer; overload;\n" + " class function MethodName(Address: Pointer): string;\n" + " class function QualifiedClassName: string;\n" - + " function FieldAddress(const Name: _ShortStr): Pointer; overload;\n" + + " function FieldAddress(const Name: ShortString): Pointer; overload;\n" + " function FieldAddress(const Name: string): Pointer; overload;\n" + " function GetInterface(const IID: TGUID; out Obj): Boolean;\n" + " class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;\n" @@ -620,6 +665,8 @@ private Path createStandardLibrary() { + " function _Release: Integer; stdcall;\n" + " end;\n" + "\n" + + " IUnknown = IInterface;\n" + + "\n" + " IEnumerator = interface(IInterface)\n" + " function GetCurrent: TObject;\n" + " function MoveNext: Boolean;\n" @@ -652,17 +699,54 @@ private Path createStandardLibrary() { + " function Equals(Value: T): Boolean;\n" + " end;\n" + "\n" + + " PLongInt = ^LongInt;\n" + + " PInteger = ^Integer;\n" + + " PCardinal = ^Cardinal;\n" + + " PWord = ^Word;\n" + + " PSmallInt = ^SmallInt;\n" + + " {$POINTERMATH ON}\n" + + " PByte = ^Byte;\n" + + " {$POINTERMATH OFF}\n" + + " PShortInt = ^ShortInt;\n" + + " PUint32 = ^Uint32;\n" + + " PInt64 = ^Int64;\n" + + " PUInt64 = ^UInt64;\n" + + " PLongWord = ^LongWord;\n" + + " PSingle = ^Single;\n" + + " PDouble = ^Double;\n" + + " PDate = ^Double;\n" + + " PWordBool = ^WordBool;\n" + + " PUnknown = ^IUnknown;\n" + + " PPUnknown = ^PUnknown;\n" + + " PInterface = ^IInterface;\n" + + " PPWideChar = ^PWideChar;\n" + + " PPChar = PPWideChar;\n" + + " PExtended = ^Extended;\n" + + " PComp = ^Comp;\n" + + " PCurrency = ^Currency;\n" + + " PVariant = ^Variant;\n" + + " POleVariant = ^OleVariant;\n" + + " PPointer = ^Pointer;\n" + + " PBoolean = ^Boolean;\n" + + " PNativeInt = ^NativeInt;\n" + + " PNativeUInt = ^NativeUInt;\n" + + " PShortString = ^ShortString;\n" + + " PAnsiString = ^AnsiString;\n" + + " PWideString = ^WideString;\n" + + " PUnicodeString = ^UnicodeString;\n" + + " PString = PUnicodeString;\n" + + "\n" + " PVarRec = ^TVarRec;\n" + " TVarRec = record\n" + " case Integer of\n" + " 0: (case Byte of\n" + " vtInteger: (VInteger: Integer);\n" + " vtBoolean: (VBoolean: Boolean);\n" - + " vtChar: (VChar: _AnsiChr);\n" + + " vtChar: (VChar: AnsiChar);\n" + " vtExtended: (VExtended: PExtended);\n" - + " vtString: (VString: _PShortStr);\n" + + " vtString: (VString: PShortString);\n" + " vtPointer: (VPointer: Pointer);\n" - + " vtPChar: (VPChar: _PAnsiChr);\n" + + " vtPChar: (VPChar: PAnsiChar);\n" + " vtObject: (VObject: TObject);\n" + " vtClass: (VClass: TClass);\n" + " vtWideChar: (VWideChar: WideChar);\n"