diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts
index a02d2ac50..b1dbe9d43 100644
--- a/src/DiagnosticMessages.ts
+++ b/src/DiagnosticMessages.ts
@@ -822,6 +822,17 @@ export let DiagnosticMessages = {
message: `Expected return statement in function`,
code: 1155,
severity: DiagnosticSeverity.Error
+ }),
+ cannotFindCallFuncFunction: (name: string, fullName: string, typeName: string) => ({
+ message: `Cannot find callfunc function '${name}' for type '${typeName}'`,
+ code: 1156,
+ data: {
+ name: name,
+ fullName: fullName,
+ typeName: typeName,
+ isCallfunc: true
+ },
+ severity: DiagnosticSeverity.Error
})
};
export const defaultMaximumTruncationLength = 160;
diff --git a/src/Program.spec.ts b/src/Program.spec.ts
index 3519a5ca0..519c30ece 100644
--- a/src/Program.spec.ts
+++ b/src/Program.spec.ts
@@ -19,7 +19,7 @@ import type { SinonSpy } from 'sinon';
import { createSandbox } from 'sinon';
import { SymbolTypeFlag } from './SymbolTypeFlag';
import { AssetFile } from './files/AssetFile';
-import type { ProvideFileEvent, CompilerPlugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeFileAddEvent, AfterFileAddEvent, BeforeFileRemoveEvent, AfterFileRemoveEvent } from './interfaces';
+import type { ProvideFileEvent, CompilerPlugin, BeforeProvideFileEvent, AfterProvideFileEvent, BeforeFileAddEvent, AfterFileAddEvent, BeforeFileRemoveEvent, AfterFileRemoveEvent, ScopeValidationOptions } from './interfaces';
import { StringType, TypedFunctionType, DynamicType, FloatType, IntegerType, InterfaceType, ArrayType, BooleanType, DoubleType, UnionType } from './types';
import { AssociativeArrayType } from './types/AssociativeArrayType';
import { ComponentType } from './types/ComponentType';
@@ -584,6 +584,161 @@ describe('Program', () => {
program.validate();
expectZeroDiagnostics(program);
});
+
+ describe('changed symbols', () => {
+ it('includes components when component interface changes', () => {
+ program.setFile('components/widget.xml', trim`
+
+
+
+
+
+ `);
+ program.setFile('components/other.xml', trim`
+
+
+
+
+
+ `);
+ program.setFile('source/main.bs', `
+ sub sourceScopeFunc()
+ end sub
+ `);
+ program.validate();
+ let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
+
+ expectZeroDiagnostics(program);
+ //change widget
+ program.setFile('components/widget.xml', trim`
+
+
+
+
+
+ `);
+ program.validate();
+ expectZeroDiagnostics(program);
+ options = program['currentScopeValidationOptions'];
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
+ });
+
+ it('includes components when component callfunc changes', () => {
+ program.setFile('components/widget.xml', trim`
+
+
+
+
+
+
+ `);
+ program.setFile('components/widget.bs', `
+ sub foo()
+ end sub
+ `);
+ program.setFile('components/other.xml', trim`
+
+
+
+
+
+ `);
+ program.setFile('source/main.bs', `
+ sub sourceScopeFunc()
+ end sub
+ `);
+ program.validate();
+ let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
+
+ expectZeroDiagnostics(program);
+ //change widget@.foo
+ program.setFile('components/widget.bs', `
+ sub foo(input)
+ print input
+ end sub
+ `);
+ program.validate();
+ expectZeroDiagnostics(program);
+ options = program['currentScopeValidationOptions'];
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
+ });
+
+ it('includes types that depend on a changed component', () => {
+ program.setFile('components/widget.xml', trim`
+
+
+
+
+
+
+ `);
+ program.setFile('components/widget.bs', `
+ sub foo()
+ end sub
+ `);
+ program.setFile('components/other.xml', trim`
+
+
+
+
+
+ `);
+ program.setFile('source/main.bs', `
+ interface IncludesWidget
+ widget as roSGNodeWidget
+ end interface
+
+ sub sourceScopeFunc()
+ end sub
+ `);
+ program.validate();
+ expectZeroDiagnostics(program);
+ let options: ScopeValidationOptions = program['currentScopeValidationOptions'];
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.true;
+
+ // change roSgNodeOther
+ program.setFile('components/other.xml', trim`
+
+
+
+
+
+ `);
+ program.validate();
+ expectZeroDiagnostics(program);
+ options = program['currentScopeValidationOptions'];
+
+ // only rosgnodewidget changes
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.false;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.false;
+
+ //change widget@.foo
+ program.setFile('components/widget.bs', `
+ sub foo(input)
+ print input
+ end sub
+ `);
+ program.validate();
+ expectZeroDiagnostics(program);
+ options = program['currentScopeValidationOptions'];
+
+ // has rosgnodewidget AND IncludesWidget, because it depends on roSgnodeWidget
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodewidget')).to.be.true;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('rosgnodeother')).to.be.false;
+ expect(options.changedSymbols.get(SymbolTypeFlag.typetime).has('includeswidget')).to.be.true;
+ });
+
+ });
+
});
describe('hasFile', () => {
diff --git a/src/Program.ts b/src/Program.ts
index b50bdae39..94e19db09 100644
--- a/src/Program.ts
+++ b/src/Program.ts
@@ -5,7 +5,7 @@ import type { CodeAction, Position, Range, SignatureInformation, Location, Docum
import type { BsConfig, FinalizedBsConfig } from './BsConfig';
import { Scope } from './Scope';
import { DiagnosticMessages } from './DiagnosticMessages';
-import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent } from './interfaces';
+import type { FileObj, SemanticToken, FileLink, ProvideHoverEvent, ProvideCompletionsEvent, Hover, ProvideDefinitionEvent, ProvideReferencesEvent, ProvideDocumentSymbolsEvent, ProvideWorkspaceSymbolsEvent, BeforeFileAddEvent, BeforeFileRemoveEvent, PrepareFileEvent, PrepareProgramEvent, ProvideFileEvent, SerializedFile, TranspileObj, SerializeFileEvent, ScopeValidationOptions } from './interfaces';
import { standardizePath as s, util } from './util';
import { XmlScope } from './XmlScope';
import { DependencyGraph } from './DependencyGraph';
@@ -235,6 +235,7 @@ export class Program {
private fileSymbolInformation = new Map();
+ private currentScopeValidationOptions: ScopeValidationOptions;
/**
* Map of typetime symbols which depend upon the key symbol
@@ -432,14 +433,16 @@ export class Program {
// There is a component that can be added - use it.
const componentScope = components[0].scope;
+ this.componentsTable.removeSymbol(symbolName);
componentScope.linkSymbolTable();
const componentType = componentScope.getComponentType();
if (componentType) {
this.componentsTable.addSymbol(symbolName, {}, componentType, SymbolTypeFlag.typetime);
}
- const isComponentTypeDifferent = !previousComponentType || isReferenceType(previousComponentType) || !componentType.isEqual(previousComponentType);
+ const typeData = {};
+ const isSameAsPrevious = previousComponentType && componentType.isEqual(previousComponentType, typeData);
+ const isComponentTypeDifferent = !previousComponentType || isReferenceType(previousComponentType) || !isSameAsPrevious;
componentScope.unlinkSymbolTable();
-
return isComponentTypeDifferent;
}
@@ -458,15 +461,19 @@ export class Program {
return;
}
const components = this.components[componentKey] || [];
- // Remove any existing symbols that match
- this.componentsTable.removeSymbol(symbolName);
- // There is a component that can be added - use it.
- if (components.length > 0) {
- const componentRefType = new ReferenceType(symbolName, symbolName, SymbolTypeFlag.typetime, () => this.componentsTable);
- if (componentRefType) {
- this.componentsTable.addSymbol(symbolName, {}, componentRefType, SymbolTypeFlag.typetime);
+ if (components.length > 0) {
+ // There is a component that can be added,
+ if (!this.componentsTable.hasSymbol(symbolName, SymbolTypeFlag.typetime)) {
+ // it doesn't already exist in the table
+ const componentRefType = new ReferenceType(symbolName, symbolName, SymbolTypeFlag.typetime, () => this.componentsTable);
+ if (componentRefType) {
+ this.componentsTable.addSymbol(symbolName, {}, componentRefType, SymbolTypeFlag.typetime);
+ }
}
+ } else {
+ // there is no component. remove from table
+ this.componentsTable.removeSymbol(symbolName);
}
}
@@ -823,6 +830,7 @@ export class Program {
//if there is a scope named the same as this file's path, remove it (i.e. xml scopes)
let scope = this.scopes[file.destPath];
if (scope) {
+ this.logger.debug('Removing associated scope', scope.name);
const scopeDisposeEvent = {
program: this,
scope: scope
@@ -845,6 +853,8 @@ export class Program {
this.dependencyGraph.removeDependency('scope:source', file.dependencyGraphKey);
}
if (isBrsFile(file)) {
+ this.logger.debug('Removing file symbol info', file.srcPath);
+
if (!keepSymbolInformation) {
this.fileSymbolInformation.delete(file.pkgPath);
}
@@ -853,8 +863,12 @@ export class Program {
//if this is a component, remove it from our components map
if (isXmlFile(file)) {
+ this.logger.debug('Unregistering component', file.srcPath);
+
this.unregisterComponent(file);
}
+ this.logger.debug('Disposing file', file.srcPath);
+
//dispose any disposable things on the file
for (const disposable of file?.disposables ?? []) {
disposable();
@@ -904,6 +918,9 @@ export class Program {
this.logger.time(LogLevel.info, ['Prebuild component types'], () => {
// cast a wide net for potential changes in components
for (const file of sortedFiles) {
+ if (file.isValidated) {
+ continue;
+ }
if (isXmlFile(file)) {
this.addDeferredComponentTypeSymbolCreation(file);
} else if (isBrsFile(file)) {
@@ -1021,7 +1038,8 @@ export class Program {
const changedTypeSymbols = changedSymbols.get(SymbolTypeFlag.typetime);
do {
foundDependentTypes = false;
- for (const changedSymbol of changedTypeSymbols) {
+ const allChangedTypesSofar = [...Array.from(changedTypeSymbols), ...Array.from(dependentTypesChanged)];
+ for (const changedSymbol of allChangedTypesSofar) {
const symbolsDependentUponChangedSymbol = this.symbolDependencies.get(changedSymbol) ?? [];
for (const symbolName of symbolsDependentUponChangedSymbol) {
if (!changedTypeSymbols.has(symbolName) && !dependentTypesChanged.has(symbolName)) {
@@ -1059,6 +1077,12 @@ export class Program {
let validationTime = 0;
let scopesValidated = 0;
let changedFiles = new Set(afterValidateFiles);
+ this.currentScopeValidationOptions = {
+ filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
+ changedSymbols: changedSymbols,
+ changedFiles: changedFiles,
+ initialValidation: this.isFirstValidation
+ };
this.logger.time(LogLevel.info, ['Validate all scopes'], () => {
//sort the scope names so we get consistent results
const scopeNames = this.getSortedScopeNames();
@@ -1072,12 +1096,7 @@ export class Program {
}
for (let scopeName of scopeNames) {
let scope = this.scopes[scopeName];
- const scopeValidated = scope.validate({
- filesToBeValidatedInScopeContext: filesToBeValidatedInScopeContext,
- changedSymbols: changedSymbols,
- changedFiles: changedFiles,
- initialValidation: this.isFirstValidation
- });
+ const scopeValidated = scope.validate(this.currentScopeValidationOptions);
if (scopeValidated) {
scopesValidated++;
}
diff --git a/src/bscPlugin/completions/CompletionsProcessor.ts b/src/bscPlugin/completions/CompletionsProcessor.ts
index b8616e87f..0469ad3f5 100644
--- a/src/bscPlugin/completions/CompletionsProcessor.ts
+++ b/src/bscPlugin/completions/CompletionsProcessor.ts
@@ -433,7 +433,7 @@ export class CompletionsProcessor {
const symbolNameLower = symbol?.name.toLowerCase();
let nameMatchesType = symbolNameLower === finalTypeNameLower;
if (isTypedFunctionType(type) && !nameMatchesType) {
- if (symbolNameLower === type.name.replace(/\./gi, '_').toLowerCase()) {
+ if (symbolNameLower === type.name?.replace(/\./gi, '_').toLowerCase()) {
nameMatchesType = true;
}
}
diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts
index 7e9a2dc23..3b54b111d 100644
--- a/src/bscPlugin/validation/ScopeValidator.spec.ts
+++ b/src/bscPlugin/validation/ScopeValidator.spec.ts
@@ -4026,6 +4026,18 @@ describe('ScopeValidator', () => {
]);
});
+ it('catches when a non-component type has callfunc invocation', () => {
+ program.setFile('source/test.bs', `
+ sub printName(widget as integer)
+ print widget@.toStr()
+ end sub
+ `);
+ program.validate();
+ expectDiagnostics(program, [
+ DiagnosticMessages.cannotFindCallFuncFunction('toStr', 'integer@.toStr', 'integer').message
+ ]);
+ });
+
it('allows to types that reference themselves', () => {
program.setFile('components/Widget.xml', trim`
@@ -4103,7 +4115,35 @@ describe('ScopeValidator', () => {
`);
program.validate();
expectDiagnostics(program, [
- DiagnosticMessages.cannotFindFunction('whatever', 'roSGNodeWidget@.whatever', 'roSGNodeWidget').message
+ DiagnosticMessages.cannotFindCallFuncFunction('whatever', 'roSGNodeWidget@.whatever', 'roSGNodeWidget').message
+ ]);
+ });
+
+ it('finds invalid func name of @ callfunc invocation', () => {
+ program.setFile('components/Widget.xml', trim`
+
+
+
+
+
+
+
+ `);
+
+ program.setFile('components/Widget.bs', `
+ function getName() as string
+ return "John Doe"
+ end function
+ `);
+
+ program.setFile('source/test.bs', `
+ sub printName(widget as roSGNodeWidget)
+ print widget@.whatever()
+ end sub
+ `);
+ program.validate();
+ expectDiagnostics(program, [
+ DiagnosticMessages.cannotFindCallFuncFunction('whatever', 'roSGNodeWidget@.whatever', 'roSGNodeWidget').message
]);
});
@@ -4131,7 +4171,7 @@ describe('ScopeValidator', () => {
`);
program.validate();
expectDiagnostics(program, [
- DiagnosticMessages.cannotFindFunction('whatever the name is', 'roSGNodeWidget@.whatever the name is', 'roSGNodeWidget').message
+ DiagnosticMessages.cannotFindCallFuncFunction('whatever the name is', 'roSGNodeWidget@.whatever the name is', 'roSGNodeWidget').message
]);
});
@@ -4184,7 +4224,7 @@ describe('ScopeValidator', () => {
`);
program.validate();
expectDiagnostics(program, [
- DiagnosticMessages.cannotFindFunction('someFunc', 'roSGNodeRectangle@.someFunc', 'roSGNodeRectangle')
+ DiagnosticMessages.cannotFindCallFuncFunction('someFunc', 'roSGNodeRectangle@.someFunc', 'roSGNodeRectangle')
]);
});
});
diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts
index 698326aac..0e364d2df 100644
--- a/src/bscPlugin/validation/ScopeValidator.ts
+++ b/src/bscPlugin/validation/ScopeValidator.ts
@@ -368,18 +368,16 @@ export class ScopeValidator {
if (!isComponentType(callerType)) {
return;
}
- const componentNameLower = callerType.toString().toLowerCase();
-
const firstArgToken = call?.args[0]?.tokens.value;
if (callName === 'createchild') {
this.checkComponentName(firstArgToken);
- } else if (callName === 'callfunc' && !['rosgnode', 'rosgnodenode'].includes(componentNameLower)) {
+ } else if (callName === 'callfunc' && !util.isGenericNodeType(callerType)) {
const funcType = util.getCallFuncType(call, firstArgToken, { flags: SymbolTypeFlag.runtime, ignoreCall: true });
if (!funcType?.isResolvable()) {
const functionName = firstArgToken.text.replace(/"/g, '');
const functionFullname = `${callerType.toString()}@.${functionName}`;
this.addMultiScopeDiagnostic({
- ...DiagnosticMessages.cannotFindFunction(functionName, functionFullname, callerType.toString()),
+ ...DiagnosticMessages.cannotFindCallFuncFunction(functionName, functionFullname, callerType.toString()),
location: firstArgToken?.location
});
} else {
@@ -402,9 +400,33 @@ export class ScopeValidator {
}
private validateCallFuncExpression(file: BrsFile, expression: CallfuncExpression) {
- const getTypeOptions = { flags: SymbolTypeFlag.runtime, data: {} };
- const funcType = this.getNodeTypeWrapper(file, expression, { ...getTypeOptions, ignoreCall: true });
+ const callerType = expression.callee?.getType({ flags: SymbolTypeFlag.runtime });
+ if (isDynamicType(callerType)) {
+ return;
+ }
+ const methodToken = expression.tokens.methodName;
+ const methodName = methodToken?.text ?? '';
+ const functionFullname = `${callerType.toString()}@.${methodName}`;
const callErrorLocation = expression.location;
+
+ if (!isComponentType(callerType)) {
+ this.addMultiScopeDiagnostic({
+ ...DiagnosticMessages.cannotFindCallFuncFunction(methodName, functionFullname, callerType.toString()),
+ location: callErrorLocation
+ });
+ return;
+ }
+ if (util.isGenericNodeType(callerType)) {
+ // ignore "general" node
+ return;
+ }
+ const funcType = util.getCallFuncType(expression, methodToken, { flags: SymbolTypeFlag.runtime, ignoreCall: true });
+ if (!funcType?.isResolvable()) {
+ this.addMultiScopeDiagnostic({
+ ...DiagnosticMessages.cannotFindCallFuncFunction(methodName, functionFullname, callerType.toString()),
+ location: callErrorLocation
+ });
+ }
return this.validateFunctionCall(file, funcType, callErrorLocation, expression.args);
}
diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts
index 18d6e5ffc..0897fc595 100644
--- a/src/files/BrsFile.spec.ts
+++ b/src/files/BrsFile.spec.ts
@@ -1833,8 +1833,8 @@ describe('BrsFile', () => {
print PKG_PATH
print LINE_NUM
print new Person()
- m@.someCallfunc()
- m@.someCallfunc(1, 2)
+ m.node@.someCallfunc()
+ m.node@.someCallfunc(1, 2)
print tag\`stuff\${LINE_NUM}\${LINE_NUM}\`
print 1 = 1 ? 1 : 2
print 1 = 1 ? m.one : m.two
@@ -1887,8 +1887,8 @@ describe('BrsFile', () => {
print "pkg:/source/main.brs"
print LINE_NUM
print Person()
- m.callfunc("someCallfunc")
- m.callfunc("someCallfunc", 1, 2)
+ m.node.callfunc("someCallfunc")
+ m.node.callfunc("someCallfunc", 1, 2)
print tag(["stuff", "", ""], [LINE_NUM, LINE_NUM])
print bslib_ternary(1 = 1, 1, 2)
print (function(__bsCondition, m)
@@ -4090,10 +4090,10 @@ describe('BrsFile', () => {
describe('callfunc operator', () => {
describe('transpile', () => {
- it('does not produce diagnostics', () => {
+ it('does not produce diagnostics on plain roSGNode', () => {
program.setFile('source/main.bs', `
sub test()
- someNode = createObject("roSGNode", "Rectangle")
+ someNode = createObject("roSGNode", "Node")
someNode@.someFunction({test: "value"})
end sub
`);
diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts
index a5d5997df..f79fe458c 100644
--- a/src/files/BrsFile.ts
+++ b/src/files/BrsFile.ts
@@ -1199,6 +1199,7 @@ export class BrsFile implements BscFile {
const previousSymbolsChecked = new Map>();
previousSymbolsChecked.set(SymbolTypeFlag.runtime, new Set());
previousSymbolsChecked.set(SymbolTypeFlag.typetime, new Set());
+
for (const flag of [SymbolTypeFlag.runtime, SymbolTypeFlag.typetime]) {
const newSymbolMapForFlag = symbolMap.get(flag);
const oldSymbolMapForFlag = previouslyProvidedSymbols?.get(flag);
diff --git a/src/files/XmlFile.ts b/src/files/XmlFile.ts
index d1e2aeac6..55b1dda8e 100644
--- a/src/files/XmlFile.ts
+++ b/src/files/XmlFile.ts
@@ -299,7 +299,7 @@ export class XmlFile implements BscFile {
private dependencyGraph: DependencyGraph;
public onDependenciesChanged(event: DependencyChangedEvent) {
- this.logDebug('clear cache because dependency graph changed');
+ this.logDebug('clear cache because dependency graph changed', event?.sourceKey);
this.cache.clear();
}
diff --git a/src/interfaces.ts b/src/interfaces.ts
index c6441ce1a..8ae8745c4 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -1076,6 +1076,8 @@ export interface TypeCompatibilityData {
// override for diagnostic message - useful for Arrays with different default types
actualType?: BscType;
expectedType?: BscType;
+ allowNameEquality?: boolean;
+ unresolveableTarget?: string;
}
export interface NamespaceContainer {
diff --git a/src/types/BscType.ts b/src/types/BscType.ts
index 85302678c..bfb54bdd1 100644
--- a/src/types/BscType.ts
+++ b/src/types/BscType.ts
@@ -74,7 +74,7 @@ export abstract class BscType {
throw new Error('Method not implemented.');
}
- checkCompatibilityBasedOnMembers(targetType: BscType, flags: SymbolTypeFlag, data: TypeCompatibilityData = {}, memberTable?: SymbolTable) {
+ checkCompatibilityBasedOnMembers(targetType: BscType, flags: SymbolTypeFlag, data: TypeCompatibilityData = {}, memberTable?: SymbolTable, targetMemberTable?: SymbolTable) {
if (!targetType) {
return false;
}
@@ -91,6 +91,7 @@ export abstract class BscType {
}
if (isReferenceType(targetType) && !targetType.isResolvable()) {
+ data.unresolveableTarget = targetType.fullName;
// we can't resolve the other type. Assume it does not fail on member checks
return true;
}
@@ -100,8 +101,9 @@ export abstract class BscType {
return false;
}
const mySymbols = (memberTable ?? this.getMemberTable())?.getAllSymbols(flags);
+ const targetTable = (targetMemberTable ?? targetType.getMemberTable());
for (const memberSymbol of mySymbols) {
- const targetTypesOfSymbol = targetType.getMemberTable()
+ const targetTypesOfSymbol = targetTable
.getSymbolTypes(memberSymbol.name, { flags: flags })
?.map(symbol => symbol.type);
if (!targetTypesOfSymbol || targetTypesOfSymbol.length === 0) {
diff --git a/src/types/ComponentType.ts b/src/types/ComponentType.ts
index e3a5efba4..d0df28912 100644
--- a/src/types/ComponentType.ts
+++ b/src/types/ComponentType.ts
@@ -16,7 +16,7 @@ export class ComponentType extends InheritableType {
constructor(public name: string, superComponent?: ComponentType) {
super(name, superComponent);
this.callFuncMemberTable = new SymbolTable(`${this.name}: CallFunc`, () => this.parentComponent?.callFuncMemberTable);
- this.callFuncAssociatedTypesTable = new SymbolTable(`${this.name}: CallFuncAssociatedTypes`);
+ this.callFuncAssociatedTypesTable = new SymbolTable(`${this.name}: CallFuncAssociatedTypes`, () => this.parentComponent?.callFuncAssociatedTypesTable);
}
public readonly kind = BscTypeKind.ComponentType;
@@ -42,7 +42,7 @@ export class ComponentType extends InheritableType {
isEqual(targetType: BscType, data: TypeCompatibilityData = {}): boolean {
if (isReferenceType(targetType) && targetType.isResolvable()) {
- targetType = targetType.getTarget();
+ targetType = targetType.getTarget?.() ?? targetType;
}
if (this === targetType) {
return true;
@@ -60,11 +60,19 @@ export class ComponentType extends InheritableType {
return true;
}
- return this.isParentTypeEqual(targetType, data) &&
- this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data) &&
- targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data) &&
- this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data, this.callFuncMemberTable) &&
- targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data, this.callFuncMemberTable);
+ if (!this.isParentTypeEqual(targetType, data)) {
+ return false;
+ }
+ if (!this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data) ||
+ !targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data)) {
+ return false;
+ }
+ if (!this.checkCompatibilityBasedOnMembers(targetType, SymbolTypeFlag.runtime, data, this.callFuncMemberTable, targetType.callFuncMemberTable) ||
+ !targetType.checkCompatibilityBasedOnMembers(this, SymbolTypeFlag.runtime, data, targetType.callFuncMemberTable, this.callFuncMemberTable)) {
+ return false;
+ }
+
+ return true;
}
public toString() {
diff --git a/src/types/InheritableType.ts b/src/types/InheritableType.ts
index 1271c87a1..b9e53fd0f 100644
--- a/src/types/InheritableType.ts
+++ b/src/types/InheritableType.ts
@@ -125,6 +125,15 @@ export abstract class InheritableType extends BscType {
if (this === targetType) {
return true;
}
+ if (data?.allowNameEquality) {
+ const thisKind = (this as any).kind;
+ if (thisKind && thisKind === (targetType as any).kind) {
+ if (this.toString().toLowerCase() === targetType.toString().toLowerCase()) {
+ return true;
+ }
+ }
+ }
+
if (this.isAncestorUnresolvedReferenceType() || targetType.isAncestorUnresolvedReferenceType()) {
return this.name.toLowerCase() === targetType.name?.toLowerCase() &&
this.isParentTypeEqual(targetType, data);
diff --git a/src/types/TypedFunctionType.ts b/src/types/TypedFunctionType.ts
index 1061e041f..89b3b7fd5 100644
--- a/src/types/TypedFunctionType.ts
+++ b/src/types/TypedFunctionType.ts
@@ -79,9 +79,15 @@ export class TypedFunctionType extends BaseFunctionType {
return 'Function';
}
- isEqual(targetType: BscType) {
+ isEqual(targetType: BscType, data: TypeCompatibilityData = {}) {
if (isTypedFunctionType(targetType)) {
- return this.checkParamsAndReturnValue(targetType, false, (t1, t2) => t1.isEqual(t2));
+ if (this.toString().toLowerCase() === targetType.toString().toLowerCase()) {
+ // this function has the same param names and types and return type as the target
+ return true;
+ }
+ return this.checkParamsAndReturnValue(targetType, false, (t1, t2, predData = {}) => {
+ return t1.isEqual(t2, { ...predData, allowNameEquality: true });
+ }, data);
}
return false;
}
diff --git a/src/util.ts b/src/util.ts
index 66b3144c2..ae424f8b8 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -2271,6 +2271,17 @@ export class Util {
return false;
}
+ public isGenericNodeType(type: BscType) {
+ if (isComponentType(type)) {
+ const lowerName = type.toString().toLowerCase();
+ if (lowerName === 'rosgnode' || lowerName === 'rosgnodenode') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
public hasAnyRequiredSymbolChanged(requiredSymbols: UnresolvedSymbol[], changedSymbols: Map>) {
if (!requiredSymbols || !changedSymbols) {
return false;