Skip to content

Commit 644e306

Browse files
authored
Allow v1 syntax: built-in types for class member types and type declarations on lhs (#1059)
1 parent 5865785 commit 644e306

File tree

3 files changed

+106
-13
lines changed

3 files changed

+106
-13
lines changed

src/files/BrsFile.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3860,6 +3860,19 @@ describe('BrsFile', () => {
38603860
`);
38613861
});
38623862

3863+
it('allows built-in types for interface members', () => {
3864+
program.setFile<BrsFile>('source/main.bs', `
3865+
interface MyBase
3866+
regex as roRegex
3867+
node as roSGNodeLabel
3868+
sub outputMatches(textInput as string)
3869+
function getLabelParent() as roSGNode
3870+
end interface
3871+
`);
3872+
program.validate();
3873+
expectZeroDiagnostics(program);
3874+
});
3875+
38633876
it('allows extends on interfaces', () => {
38643877
testTranspile(`
38653878
interface MyBase
@@ -3886,5 +3899,45 @@ describe('BrsFile', () => {
38863899
program.validate();
38873900
expectZeroDiagnostics(program);
38883901
});
3902+
3903+
it('allows built-in types for class members', () => {
3904+
program.setFile<BrsFile>('source/main.bs', `
3905+
class MyBase
3906+
regex as roRegex
3907+
node as roSGNodeLabel
3908+
3909+
sub outputMatches(textInput as string)
3910+
matches = m.regex.match(textInput)
3911+
if matches.count() > 1
3912+
m.node.text = matches[1]
3913+
else
3914+
m.node.text = "no match"
3915+
end if
3916+
end sub
3917+
3918+
function getLabelParent() as roSGNode
3919+
return m.node.getParent()
3920+
end function
3921+
end class
3922+
`);
3923+
program.validate();
3924+
expectZeroDiagnostics(program);
3925+
});
3926+
3927+
it('allows types on lhs of assignments', () => {
3928+
testTranspile(`
3929+
sub foo(node as roSGNode)
3930+
nodeParent as roSGNode = node.getParent()
3931+
text as string = nodeParent.id
3932+
print text
3933+
end sub
3934+
`, `
3935+
sub foo(node as object)
3936+
nodeParent = node.getParent()
3937+
text = nodeParent.id
3938+
print text
3939+
end sub
3940+
`);
3941+
});
38893942
});
38903943
});

src/parser/Parser.ts

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,14 @@ export class Parser {
10381038
range: name.range
10391039
});
10401040
}
1041+
if (this.check(TokenKind.As)) {
1042+
// v1 syntax allows type declaration on lhs of assignment
1043+
this.warnIfNotBrighterScriptMode('typed assignment');
1044+
1045+
this.advance(); // skip 'as'
1046+
this.typeToken(); // skip typeToken;
1047+
}
1048+
10411049
let operator = this.consume(
10421050
DiagnosticMessages.expectedOperatorAfterIdentifier(AssignmentOperators, name.text),
10431051
...AssignmentOperators
@@ -1170,10 +1178,33 @@ export class Parser {
11701178
// `let`, (...) keyword. As such, we must check the token *after* an identifier to figure
11711179
// out what to do with it.
11721180
if (
1173-
this.checkAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers) &&
1174-
this.checkAnyNext(...AssignmentOperators)
1181+
this.checkAny(TokenKind.Identifier, ...this.allowedLocalIdentifiers)
11751182
) {
1176-
return this.assignment();
1183+
if (this.checkAnyNext(...AssignmentOperators)) {
1184+
return this.assignment();
1185+
} else if (this.checkNext(TokenKind.As)) {
1186+
// may be a typed assignment - this is v1 syntax
1187+
const backtrack = this.current;
1188+
let validTypeExpression = false;
1189+
try {
1190+
// skip the identifier, and check for valid type expression
1191+
this.advance();
1192+
// skip the 'as'
1193+
this.advance();
1194+
// check if there is a valid type
1195+
const typeToken = this.typeToken(true);
1196+
const allowedNameKinds = [TokenKind.Identifier, ...DeclarableTypes, ...this.allowedLocalIdentifiers];
1197+
validTypeExpression = allowedNameKinds.includes(typeToken.kind);
1198+
} catch (e) {
1199+
// ignore any errors
1200+
} finally {
1201+
this.current = backtrack;
1202+
}
1203+
if (validTypeExpression) {
1204+
// there is a valid 'as' and type expression
1205+
return this.assignment();
1206+
}
1207+
}
11771208
}
11781209

11791210
//some BrighterScript keywords are allowed as a local identifiers, so we need to check for them AFTER the assignment check
@@ -1388,13 +1419,21 @@ export class Parser {
13881419
/**
13891420
* Get an expression with identifiers separated by periods. Useful for namespaces and class extends
13901421
*/
1391-
private getNamespacedVariableNameExpression() {
1392-
let firstIdentifier = this.consume(
1393-
DiagnosticMessages.expectedIdentifierAfterKeyword(this.previous().text),
1394-
TokenKind.Identifier,
1395-
...this.allowedLocalIdentifiers
1396-
) as Identifier;
1397-
1422+
private getNamespacedVariableNameExpression(ignoreDiagnostics = false) {
1423+
let firstIdentifier: Identifier;
1424+
if (ignoreDiagnostics) {
1425+
if (this.checkAny(...this.allowedLocalIdentifiers)) {
1426+
firstIdentifier = this.advance() as Identifier;
1427+
} else {
1428+
throw new Error();
1429+
}
1430+
} else {
1431+
firstIdentifier = this.consume(
1432+
DiagnosticMessages.expectedIdentifierAfterKeyword(this.previous().text),
1433+
TokenKind.Identifier,
1434+
...this.allowedLocalIdentifiers
1435+
) as Identifier;
1436+
}
13981437
let expr: DottedGetExpression | VariableExpression;
13991438

14001439
if (firstIdentifier) {
@@ -2618,7 +2657,7 @@ export class Parser {
26182657
* Will return a token of whatever is next to be parsed
26192658
* Will allow v1 type syntax (typed arrays, union types), but there is no validation on types used this way
26202659
*/
2621-
private typeToken(): Token {
2660+
private typeToken(ignoreDiagnostics = false): Token {
26222661
let typeToken: Token;
26232662
let lookForUnions = true;
26242663
let isAUnion = false;
@@ -2632,7 +2671,7 @@ export class Parser {
26322671
} else if (this.options.mode === ParseMode.BrighterScript) {
26332672
try {
26342673
// see if we can get a namespaced identifer
2635-
const qualifiedType = this.getNamespacedVariableNameExpression();
2674+
const qualifiedType = this.getNamespacedVariableNameExpression(ignoreDiagnostics);
26362675
typeToken = createToken(TokenKind.Identifier, qualifiedType.getName(this.options.mode), qualifiedType.range);
26372676
} catch {
26382677
//could not get an identifier - just get whatever's next

src/validators/ClassValidator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,8 @@ export class BsClassValidator {
324324
const namespace = classStatement.findAncestor<NamespaceStatement>(isNamespaceStatement);
325325
const currentNamespaceName = namespace?.getName(ParseMode.BrighterScript);
326326
//check if this custom type is in our class map
327-
if (!this.getClassByName(lowerFieldTypeName, currentNamespaceName) && !this.scope.hasInterface(lowerFieldTypeName) && !this.scope.hasEnum(lowerFieldTypeName)) {
327+
const isBuiltInType = util.isBuiltInType(lowerFieldTypeName);
328+
if (!isBuiltInType && !this.getClassByName(lowerFieldTypeName, currentNamespaceName) && !this.scope.hasInterface(lowerFieldTypeName) && !this.scope.hasEnum(lowerFieldTypeName)) {
328329
this.diagnostics.push({
329330
...DiagnosticMessages.cannotFindType(fieldTypeName),
330331
range: statement.type?.range ?? statement.range,

0 commit comments

Comments
 (0)