1- import { ValidationChecks , ValidationRegistry } from 'langium' ;
2- import { LoxAstType } from './generated/ast' ;
1+ import { AstNode , streamAllContents , ValidationAcceptor , ValidationChecks , ValidationRegistry } from 'langium' ;
2+ import { BinaryExpression , ExpressionBlock , FunctionDeclaration , isReturnStatement , LoxAstType , MethodMember , TypeReference , UnaryExpression , VariableDeclaration } from './generated/ast' ;
33import type { LoxServices } from './lox-module' ;
4+ import { isAssignable } from './type-system/assignment' ;
5+ import { isVoidType , TypeDescription , typeToString } from './type-system/descriptions' ;
6+ import { inferType } from './type-system/infer' ;
7+ import { isLegalOperation } from './type-system/operator' ;
48
59/**
610 * Registry for validation checks.
@@ -10,6 +14,11 @@ export class LoxValidationRegistry extends ValidationRegistry {
1014 super ( services ) ;
1115 const validator = services . validation . LoxValidator ;
1216 const checks : ValidationChecks < LoxAstType > = {
17+ BinaryExpression : validator . checkBinaryOperationAllowed ,
18+ UnaryExpression : validator . checkUnaryOperationAllowed ,
19+ VariableDeclaration : validator . checkVariableDeclaration ,
20+ MethodMember : validator . checkMethodReturnType ,
21+ FunctionDeclaration : validator . checkFunctionReturnType
1322 } ;
1423 this . register ( checks , validator ) ;
1524 }
@@ -20,13 +29,89 @@ export class LoxValidationRegistry extends ValidationRegistry {
2029 */
2130export class LoxValidator {
2231
23- // checkPersonStartsWithCapital(person: Person, accept: ValidationAcceptor): void {
24- // if (person.name) {
25- // const firstChar = person.name.substring(0, 1);
26- // if (firstChar.toUpperCase() !== firstChar) {
27- // accept('warning', 'Person name should start with a capital.', { node: person, property: 'name' });
28- // }
29- // }
30- // }
32+ checkFunctionReturnType ( func : FunctionDeclaration , accept : ValidationAcceptor ) : void {
33+ this . checkFunctionReturnTypeInternal ( func . body , func . returnType , accept ) ;
34+ }
35+
36+ checkMethodReturnType ( method : MethodMember , accept : ValidationAcceptor ) : void {
37+ this . checkFunctionReturnTypeInternal ( method . body , method . returnType , accept ) ;
38+ }
39+
40+ private checkFunctionReturnTypeInternal ( body : ExpressionBlock , returnType : TypeReference , accept : ValidationAcceptor ) : void {
41+ const map = this . getTypeCache ( ) ;
42+ const returnStatements = streamAllContents ( body ) . filter ( isReturnStatement ) . toArray ( ) ;
43+ const expectedType = inferType ( returnType , map ) ;
44+ if ( returnStatements . length === 0 && ! isVoidType ( expectedType ) ) {
45+ accept ( 'error' , "A function whose declared type is not 'void' must return a value." , {
46+ node : returnType
47+ } ) ;
48+ return ;
49+ }
50+ for ( const returnStatement of returnStatements ) {
51+ const returnValueType = inferType ( returnStatement , map ) ;
52+ if ( ! isAssignable ( returnValueType , expectedType ) ) {
53+ accept ( 'error' , `Type '${ typeToString ( returnValueType ) } ' is not assignable to type '${ typeToString ( expectedType ) } '.` , {
54+ node : returnStatement
55+ } ) ;
56+ }
57+ }
58+ }
59+
60+ checkVariableDeclaration ( decl : VariableDeclaration , accept : ValidationAcceptor ) : void {
61+ if ( decl . type && decl . value ) {
62+ const map = this . getTypeCache ( ) ;
63+ const left = inferType ( decl . type , map ) ;
64+ const right = inferType ( decl . value , map ) ;
65+ if ( ! isAssignable ( right , left ) ) {
66+ accept ( 'error' , `Type '${ typeToString ( right ) } ' is not assignable to type '${ typeToString ( left ) } '.` , {
67+ node : decl ,
68+ property : 'value'
69+ } ) ;
70+ }
71+ } else if ( ! decl . type && ! decl . value ) {
72+ accept ( 'error' , 'Variables require a type hint or an assignment at creation' , {
73+ node : decl ,
74+ property : 'name'
75+ } ) ;
76+ }
77+ }
78+
79+ checkBinaryOperationAllowed ( binary : BinaryExpression , accept : ValidationAcceptor ) : void {
80+ const map = this . getTypeCache ( ) ;
81+ const left = inferType ( binary . left , map ) ;
82+ const right = inferType ( binary . right , map ) ;
83+ if ( ! isLegalOperation ( binary . operator , left , right ) ) {
84+ accept ( 'error' , `Cannot perform operation '${ binary . operator } ' on values of type '${ typeToString ( left ) } ' and '${ typeToString ( right ) } '.` , {
85+ node : binary
86+ } )
87+ } else if ( binary . operator === '=' ) {
88+ if ( ! isAssignable ( right , left ) ) {
89+ accept ( 'error' , `Type '${ typeToString ( right ) } ' is not assignable to type '${ typeToString ( left ) } '.` , {
90+ node : binary ,
91+ property : 'right'
92+ } )
93+ }
94+ } else if ( [ '==' , '!=' ] . includes ( binary . operator ) ) {
95+ if ( ! isAssignable ( right , left ) ) {
96+ accept ( 'warning' , `This comparison will always return '${ binary . operator === '==' ? 'false' : 'true' } ' as types '${ typeToString ( left ) } ' and '${ typeToString ( right ) } ' are not compatible.` , {
97+ node : binary ,
98+ property : 'operator'
99+ } ) ;
100+ }
101+ }
102+ }
103+
104+ checkUnaryOperationAllowed ( unary : UnaryExpression , accept : ValidationAcceptor ) : void {
105+ const item = inferType ( unary . value , this . getTypeCache ( ) ) ;
106+ if ( ! isLegalOperation ( unary . operator , item ) ) {
107+ accept ( 'error' , `Cannot perform operation '${ unary . operator } ' on value of type '${ typeToString ( item ) } '.` , {
108+ node : unary
109+ } ) ;
110+ }
111+ }
112+
113+ private getTypeCache ( ) : Map < AstNode , TypeDescription > {
114+ return new Map ( ) ;
115+ }
31116
32117}
0 commit comments