Skip to content

Commit fcd8d26

Browse files
CreateObject function call implies correct return type (#1154)
* Works for built in types... not custom components yet * Adds Type Inference for createObject function call * Added tests for default case * Reverts callback on TypedFunctionType in favor of special case handling for Cretaeobject calls --------- Co-authored-by: Bronley Plumb <bronley@gmail.com>
1 parent 9ec2970 commit fcd8d26

File tree

5 files changed

+100
-11
lines changed

5 files changed

+100
-11
lines changed

src/Scope.spec.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ import { BooleanType } from './types/BooleanType';
1717
import { StringType } from './types/StringType';
1818
import { IntegerType } from './types/IntegerType';
1919
import { DynamicType } from './types/DynamicType';
20-
import { ObjectType } from './types/ObjectType';
2120
import { FloatType } from './types/FloatType';
2221
import { NamespaceType } from './types/NamespaceType';
2322
import { DoubleType } from './types/DoubleType';
2423
import { UnionType } from './types/UnionType';
25-
import { isForEachStatement, isFunctionExpression, isFunctionStatement, isNamespaceStatement } from './astUtils/reflection';
24+
import { isBlock, isForEachStatement, isFunctionExpression, isFunctionStatement, isNamespaceStatement } from './astUtils/reflection';
2625
import { ArrayType } from './types/ArrayType';
2726
import { AssociativeArrayType } from './types/AssociativeArrayType';
2827
import { InterfaceType } from './types/InterfaceType';
2928
import { ComponentType } from './types/ComponentType';
3029
import * as path from 'path';
3130
import { WalkMode, createVisitor } from './astUtils/visitors';
3231
import type { FunctionExpression } from './parser/Expression';
32+
import { ObjectType } from './types';
3333

3434
describe('Scope', () => {
3535
let sinon = sinonImport.createSandbox();
@@ -800,6 +800,71 @@ describe('Scope', () => {
800800
DiagnosticMessages.unknownBrightScriptComponent('roFontMetrics')
801801
]);
802802
});
803+
804+
it('infers the correct type', () => {
805+
const file = program.setFile<BrsFile>(`source/file.brs`, `
806+
sub main()
807+
scene = CreateObject("roSGScreen")
808+
button = CreateObject("roSGNode", "Button")
809+
list = CreateObject("roSGNode", "MarkupList")
810+
end sub
811+
`);
812+
program.validate();
813+
expectZeroDiagnostics(program);
814+
const mainSymbolTable = file.ast.findChild(isBlock).getSymbolTable();
815+
const sceneType = mainSymbolTable.getSymbolType('scene', { flags: SymbolTypeFlag.runtime }) as InterfaceType;
816+
expectTypeToBe(sceneType, InterfaceType);
817+
expect(sceneType.name).to.eq('roSGScreen');
818+
const buttonType = mainSymbolTable.getSymbolType('button', { flags: SymbolTypeFlag.runtime }) as InterfaceType;
819+
expectTypeToBe(buttonType, ComponentType);
820+
expect(buttonType.name).to.eq('Button');
821+
const listType = mainSymbolTable.getSymbolType('list', { flags: SymbolTypeFlag.runtime }) as InterfaceType;
822+
expectTypeToBe(listType, ComponentType);
823+
expect(listType.name).to.eq('MarkupList');
824+
});
825+
826+
it('infers custom component types', () => {
827+
program.setFile('components/Comp1.xml', trim`
828+
<?xml version="1.0" encoding="utf-8" ?>
829+
<component name="Comp1" extends="Group">
830+
</component>
831+
`);
832+
program.setFile('components/Comp2.xml', trim`
833+
<?xml version="1.0" encoding="utf-8" ?>
834+
<component name="Comp2" extends="Poster">
835+
</component>
836+
`);
837+
const file = program.setFile<BrsFile>(`source/file.brs`, `
838+
sub main()
839+
comp1 = CreateObject("roSGNode", "Comp1")
840+
comp2 = CreateObject("roSGNode", "Comp2")
841+
end sub
842+
`);
843+
program.validate();
844+
expectZeroDiagnostics(program);
845+
program.getScopeByName('source').linkSymbolTable();
846+
const mainSymbolTable = file.ast.findChild(isBlock).getSymbolTable();
847+
const comp1Type = mainSymbolTable.getSymbolType('comp1', { flags: SymbolTypeFlag.runtime }) as InterfaceType;
848+
expectTypeToBe(comp1Type, ComponentType);
849+
expect(comp1Type.name).to.eq('Comp1');
850+
const comp2Type = mainSymbolTable.getSymbolType('comp2', { flags: SymbolTypeFlag.runtime }) as InterfaceType;
851+
expectTypeToBe(comp2Type, ComponentType);
852+
expect(comp2Type.name).to.eq('Comp2');
853+
});
854+
855+
it('implies objectType by default', () => {
856+
const file = program.setFile<BrsFile>(`source/file.brs`, `
857+
function getObj(myObjName)
858+
result = CreateObject(myObjName)
859+
return result
860+
end function
861+
`);
862+
program.validate();
863+
expectZeroDiagnostics(program);
864+
const mainSymbolTable = file.ast.findChild(isBlock).getSymbolTable();
865+
const resultType = mainSymbolTable.getSymbolType('result', { flags: SymbolTypeFlag.runtime });
866+
expectTypeToBe(resultType, ObjectType);
867+
});
803868
});
804869

805870
it('marks the scope as validated after validation has occurred', () => {
@@ -2589,9 +2654,9 @@ describe('Scope', () => {
25892654
expect(mainFnScope).to.exist;
25902655
const getTypeOptions = { flags: SymbolTypeFlag.runtime };
25912656
let dtType = mainFnScope.symbolTable.getSymbolType('dt', getTypeOptions);
2592-
expectTypeToBe(dtType, ObjectType);
2657+
expectTypeToBe(dtType, InterfaceType);
25932658
let hoursType = mainFnScope.symbolTable.getSymbolType('hours', getTypeOptions);
2594-
expectTypeToBe(hoursType, DynamicType);
2659+
expectTypeToBe(hoursType, IntegerType);
25952660
});
25962661

25972662
describe('union types', () => {

src/files/tests/optionalChaning.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ describe('optional chaining', () => {
8080
print arr?.value
8181
print obj?.[0]
8282
print obj?.getName()?.first?.second
83-
print createObject("roByteArray")?.value
83+
print createObject("roByteArray")?.Count
8484
print createObject("roByteArray")?["0"]
85-
print createObject("roList")?.value
85+
print createObject("roList")?.Count
8686
print createObject("roList")?["0"]
8787
print createObject("roXmlList")?["0"]
88-
print createObject("roDateTime")?.value
88+
print createObject("roDateTime")?.GetYear
8989
print createObject("roDateTime")?.GetTimeZoneOffset
9090
print createObject("roSGNode", "Node")?[0]
9191
print obj?.first?.second

src/parser/Expression.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ export class CallExpression extends Expression {
112112

113113
constructor(options: {
114114
callee: Expression;
115-
openingParen: Token;
115+
openingParen?: Token;
116116
args?: Expression[];
117-
closingParen: Token;
117+
closingParen?: Token;
118118
}) {
119119
super();
120120
this.tokens = {
@@ -184,6 +184,10 @@ export class CallExpression extends Expression {
184184
if (isNewExpression(this.parent)) {
185185
return calleeType;
186186
}
187+
const specialCaseReturnType = util.getSpecialCaseCallExpressionReturnType(this);
188+
if (specialCaseReturnType) {
189+
return specialCaseReturnType;
190+
}
187191
if (isCallableType(calleeType) && (!isReferenceType(calleeType.returnType) || calleeType.returnType?.isResolvable())) {
188192
return calleeType.returnType;
189193
}

src/types/TypedFunctionType.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,5 @@ describe('TypedFunctionType', () => {
154154
let variFunc2 = new TypedFunctionType(DynamicType.instance);
155155
variFunc2.isVariadic = true;
156156
expect(variFunc1.isTypeCompatible(variFunc2)).to.be.true;
157-
158157
});
159158
});

src/util.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import type { CallExpression, CallfuncExpression, DottedGetExpression, FunctionP
2626
import { Logger, LogLevel } from './Logger';
2727
import { isToken, type Identifier, type Locatable, type Token } from './lexer/Token';
2828
import { TokenKind } from './lexer/TokenKind';
29-
import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLongIntegerType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection';
29+
import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLiteralString, isLongIntegerType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection';
3030
import { WalkMode } from './astUtils/visitors';
3131
import { SourceNode } from 'source-map';
3232
import * as requireRelative from 'require-relative';
@@ -2127,6 +2127,27 @@ export class Util {
21272127
}
21282128
return false;
21292129
}
2130+
2131+
public getSpecialCaseCallExpressionReturnType(callExpr: CallExpression) {
2132+
if (isVariableExpression(callExpr.callee) && callExpr.callee.tokens.name.text.toLowerCase() === 'createobject') {
2133+
const componentName = isLiteralString(callExpr.args[0]) ? callExpr.args[0].tokens.value?.text?.replace(/"/g, '') : '';
2134+
const nodeType = componentName.toLowerCase() === 'rosgnode' && isLiteralString(callExpr.args[1]) ? callExpr.args[1].tokens.value?.text?.replace(/"/g, '') : '';
2135+
if (componentName?.toLowerCase().startsWith('ro')) {
2136+
const fullName = componentName + nodeType;
2137+
const data = {};
2138+
const symbolTable = callExpr.getSymbolTable();
2139+
const foundType = symbolTable.getSymbolType(fullName, {
2140+
flags: SymbolTypeFlag.typetime,
2141+
data: data,
2142+
tableProvider: () => callExpr?.getSymbolTable(),
2143+
fullName: fullName
2144+
});
2145+
if (foundType) {
2146+
return foundType;
2147+
}
2148+
}
2149+
}
2150+
}
21302151
}
21312152

21322153
/**

0 commit comments

Comments
 (0)