Skip to content

Commit 7fb92ff

Browse files
authored
Fixes How BinaryOperators work on Unions (#1400)
* Binary operators on Unions work better now * Used same logic on unary operators as well * Updated test to check result type of complex binrary operation * Binary and Unary operators handle ObjectType - issue #1387
1 parent d3ff753 commit 7fb92ff

File tree

12 files changed

+145
-10
lines changed

12 files changed

+145
-10
lines changed

src/bscPlugin/hover/HoverProcessor.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,16 +829,16 @@ describe('HoverProcessor', () => {
829829
if deviceInfo.getClockFormat() = "24h" then
830830
hour = stringUtil.pad(hour)
831831
' "22:01" | "01:01"
832-
return substitute("{0}:{1}", hour, minutes as string)
832+
return substitute("{0}:{1}", hour as string, minutes as string)
833833
else
834834
hour = hour mod 12
835835
if hour = 0 then hour = 12
836836
' "10:01 AM" | "1:01 AM"
837-
return substitute("{0}:{1} {2}", hour, minutes as string, meridiem)
837+
return substitute("{0}:{1} {2}", hour.toStr(), minutes as string, meridiem)
838838
end if
839839
end function
840840
`);
841-
const expectedHourHoverStr = `hour as dynamic`;
841+
const expectedHourHoverStr = `hour as integer or string`;
842842

843843
program.validate();
844844
expectZeroDiagnostics(program);

src/bscPlugin/validation/ScopeValidator.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3487,6 +3487,18 @@ describe('ScopeValidator', () => {
34873487
DiagnosticMessages.operatorTypeMismatch('=', 'uninitialized', 'invalid').message
34883488
]);
34893489
});
3490+
3491+
it('allows string comparisons with object', () => {
3492+
program.setFile<BrsFile>('source/main.brs', `
3493+
sub test(x as object)
3494+
if x <> "test"
3495+
print x
3496+
end if
3497+
end sub
3498+
`);
3499+
program.validate();
3500+
expectZeroDiagnostics(program);
3501+
});
34903502
});
34913503

34923504
describe('memberAccessibilityMismatch', () => {

src/files/BrsFile.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { SymbolTypeFlag } from '../SymbolTypeFlag';
2424
import { ClassType, EnumType, FloatType, InterfaceType } from '../types';
2525
import type { StandardizedFileEntry } from 'roku-deploy';
2626
import * as fileUrl from 'file-url';
27-
import { isAALiteralExpression, isBlock } from '../astUtils/reflection';
27+
import { isAALiteralExpression, isBlock, isFunctionExpression } from '../astUtils/reflection';
2828
import type { AALiteralExpression } from '../parser/Expression';
2929
import { CallExpression, FunctionExpression, LiteralExpression } from '../parser/Expression';
3030
import { Logger } from '@rokucommunity/logger';
@@ -192,7 +192,7 @@ describe('BrsFile', () => {
192192
it('does not crazy during validation with unique binary operator', () => {
193193
//monitor the logging system, if we detect an error, this test fails
194194
const spy = sinon.spy(Logger.prototype, 'error');
195-
program.setFile('source/main.bs', `
195+
const file = program.setFile<BrsFile>('source/main.bs', `
196196
namespace date
197197
function timeElapsedInDay()
198198
time = 1
@@ -215,6 +215,14 @@ describe('BrsFile', () => {
215215
expect(
216216
spy.getCalls().map(x => (x.args?.[0] as string)?.toString()).filter(x => x?.includes('Error when calling plugin'))
217217
).to.eql([]);
218+
219+
// Check the result type too
220+
const sourceScope = program.getScopeByName('source');
221+
sourceScope.linkSymbolTable();
222+
const timeElapsedFunc = file.ast.findChild<FunctionExpression>(isFunctionExpression);
223+
const symbolTable = timeElapsedFunc.body.getSymbolTable();
224+
const offsetType = symbolTable.getSymbolType('offset', { flags: SymbolTypeFlag.runtime });
225+
expectTypeToBe(offsetType, IntegerType);
218226
});
219227

220228
it('supports the third parameter in CreateObject', () => {

src/types/BscType.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,11 @@ export abstract class BscType {
139139
}
140140
this.hasAddedBuiltInInterfaces = true;
141141
}
142+
143+
/**
144+
* The level of priority of this type when in a binary operation
145+
* For example Float is higher priority than integer, so Float + Int => Float
146+
* Lower numbers have higher priority
147+
*/
148+
readonly binaryOpPriorityLevel: number = 0;
142149
}

src/types/DoubleType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export class DoubleType extends BscType {
4141
public isEqual(targetType: BscType): boolean {
4242
return isDoubleType(targetType);
4343
}
44+
45+
readonly binaryOpPriorityLevel = 1;
4446
}
4547

4648
BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('double', DoubleType.instance);

src/types/DynamicType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export class DynamicType extends BscType {
5050
getMemberType(memberName: string, options: GetTypeOptions) {
5151
return DynamicType.instance;
5252
}
53+
54+
5355
}
5456

5557
BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('dynamic', DynamicType.instance);

src/types/FloatType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export class FloatType extends BscType {
4242
public isEqual(targetType: BscType): boolean {
4343
return isFloatType(targetType);
4444
}
45+
46+
readonly binaryOpPriorityLevel = 2;
4547
}
4648

4749
BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('float', FloatType.instance);

src/types/IntegerType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export class IntegerType extends BscType {
4141
isEqual(otherType: BscType) {
4242
return isIntegerType(otherType);
4343
}
44+
45+
readonly binaryOpPriorityLevel = 4;
4446
}
4547

4648
BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('integer', IntegerType.instance);

src/types/LongIntegerType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export class LongIntegerType extends BscType {
4141
isEqual(targetType: BscType): boolean {
4242
return isLongIntegerType(targetType);
4343
}
44+
45+
readonly binaryOpPriorityLevel = 3;
4446
}
4547

4648
BuiltInInterfaceAdder.primitiveTypeInstanceCache.set('longinteger', LongIntegerType.instance);

src/types/UnionType.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class UnionType extends BscType {
6464
}
6565

6666
isTypeCompatible(targetType: BscType, data?: TypeCompatibilityData): boolean {
67-
if (isDynamicType(targetType) || isObjectType(targetType)) {
67+
if (isDynamicType(targetType) || isObjectType(targetType) || this === targetType) {
6868
return true;
6969
}
7070
if (isEnumTypeCompatible(this, targetType, data)) {
@@ -86,7 +86,6 @@ export class UnionType extends BscType {
8686
}
8787
}
8888

89-
9089
return false;
9190
}
9291
toString(): string {
@@ -106,6 +105,9 @@ export class UnionType extends BscType {
106105
if (!isUnionType(targetType)) {
107106
return false;
108107
}
108+
if (this === targetType) {
109+
return true;
110+
}
109111
return this.isTypeCompatible(targetType) && targetType.isTypeCompatible(this);
110112
}
111113

0 commit comments

Comments
 (0)