Skip to content

Commit d0bba92

Browse files
committed
Add type-checking for assignments
1 parent 1ae6eb9 commit d0bba92

File tree

7 files changed

+369
-0
lines changed

7 files changed

+369
-0
lines changed

src/config/default-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const DEFAULT_DZNLINT_CONFIG: DefaultDznLintConfig = {
3535
port_missing_redundant_blocking: false,
3636
port_parameter_direction: "error",
3737
trailing_assignments: false,
38+
type_check: false,
3839
};
3940

4041
export const DEFAULT_DZNLINT_FORMAT_CONFIG: DznLintFormatConfiguration = {

src/config/dznlint-configuration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface DznLintConfiguration {
4646
port_missing_redundant_blocking: ConfigValue;
4747
port_parameter_direction: ConfigValue;
4848
trailing_assignments: ConfigValue;
49+
type_check: ConfigValue;
4950
}
5051

5152
export type UserRuleConfig<TRule extends keyof DznLintConfiguration> =

src/linting-rule.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import no_unused_instances from "./rules/no-unused-instances";
4040
import { port_missing_redundant_blocking } from "./rules/port-missing-redundant-blocking";
4141
import port_parameter_direction from "./rules/port-parameter-direction";
4242
import trailing_assignments from "./rules/trailing-assignments";
43+
import type_check from "./rules/type-check";
4344

4445
export function loadLinters(config: DznLintUserConfiguration) {
4546
const factories = [
@@ -71,6 +72,7 @@ export function loadLinters(config: DznLintUserConfiguration) {
7172
port_missing_redundant_blocking,
7273
port_parameter_direction,
7374
trailing_assignments,
75+
type_check,
7476
];
7577

7678
const linters = new Map<ast.SyntaxKind, Linter<ast.AnyAstNode>[]>();

src/rules/type-check.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Types assigned to variables and function arguments must match
2+
3+
import * as ast from "../grammar/ast";
4+
import { getRuleConfig } from "../config/util";
5+
import { createDiagnosticsFactory } from "../diagnostic";
6+
import { RuleFactory } from "../linting-rule";
7+
import { InputSource } from "../semantics/program";
8+
import { findFirstParent, isDollarsLiteral, isEvent, isFunctionDefinition } from "../util";
9+
import { Type } from "../semantics";
10+
11+
export const typeMismatch = createDiagnosticsFactory();
12+
13+
export const type_check: RuleFactory = factoryContext => {
14+
const config = getRuleConfig("type_check", factoryContext.userConfig);
15+
16+
if (config.isEnabled) {
17+
const createTypeMismatchDiagnostic = (
18+
expectedType: Type,
19+
actualType: Type,
20+
source: InputSource,
21+
position: ast.SourceRange
22+
) => {
23+
return typeMismatch(
24+
config.severity,
25+
`Type mismatch: Expected type '${expectedType.name}', but instead got something of type '${actualType.name}'`,
26+
source,
27+
position
28+
);
29+
};
30+
31+
factoryContext.registerRule<ast.VariableDefinition>(ast.SyntaxKind.VariableDefinition, (node, context) => {
32+
// initializer type must match lhs type
33+
if (node.initializer && !isDollarsLiteral(node.initializer)) {
34+
const initializerType = context.typeChecker.typeOfNode(node.initializer);
35+
const variableType = context.typeChecker.typeOfNode(node.type);
36+
37+
if (initializerType !== variableType) {
38+
return [
39+
createTypeMismatchDiagnostic(
40+
variableType,
41+
initializerType,
42+
context.source,
43+
node.initializer.position
44+
),
45+
];
46+
}
47+
}
48+
49+
return [];
50+
});
51+
52+
factoryContext.registerRule<ast.AssignmentStatement>(ast.SyntaxKind.AssignmentStatement, (node, context) => {
53+
// rhs type must match lhs type
54+
if (!isDollarsLiteral(node.right)) {
55+
const rhsType = context.typeChecker.typeOfNode(node.right);
56+
const lhsType = context.typeChecker.typeOfNode(node.left);
57+
58+
if (rhsType !== lhsType) {
59+
return [createTypeMismatchDiagnostic(lhsType, rhsType, context.source, node.right.position)];
60+
}
61+
}
62+
63+
return [];
64+
});
65+
66+
factoryContext.registerRule<ast.CallExpression>(ast.SyntaxKind.CallExpression, (node, context) => {
67+
const diagnostics = [];
68+
69+
const functionSymbol = context.typeChecker.symbolOfNode(node.expression);
70+
let parameters: Array<ast.EventParameter | ast.FunctionParameter> = [];
71+
if (
72+
functionSymbol?.declaration &&
73+
(isEvent(functionSymbol.declaration) || isFunctionDefinition(functionSymbol.declaration))
74+
) {
75+
parameters = functionSymbol.declaration.parameters;
76+
}
77+
78+
// rhs type must match lhs type
79+
let parameterIndex = 0;
80+
for (const argument of node.arguments.arguments) {
81+
if (!isDollarsLiteral(argument) && parameterIndex < parameters.length) {
82+
const argumentType = context.typeChecker.typeOfNode(argument);
83+
const parameterType = context.typeChecker.typeOfNode(parameters[parameterIndex]);
84+
85+
if (argumentType !== parameterType) {
86+
diagnostics.push(
87+
createTypeMismatchDiagnostic(parameterType, argumentType, context.source, argument.position)
88+
);
89+
}
90+
}
91+
parameterIndex++;
92+
}
93+
94+
return diagnostics;
95+
});
96+
97+
factoryContext.registerRule<ast.ReturnStatement>(ast.SyntaxKind.ReturnStatement, (node, context) => {
98+
if (!node.returnValue || isDollarsLiteral(node.returnValue)) return [];
99+
100+
const parentFunc = findFirstParent(node, isFunctionDefinition);
101+
102+
if (!parentFunc) return []; // Would be weird to have a return not in a parent
103+
104+
const returnType = context.typeChecker.typeOfNode(parentFunc.returnType);
105+
const returendType = context.typeChecker.typeOfNode(node.returnValue);
106+
107+
if (returendType != returnType) {
108+
return [
109+
createTypeMismatchDiagnostic(returnType, returendType, context.source, node.returnValue.position),
110+
];
111+
}
112+
113+
return [];
114+
});
115+
}
116+
};
117+
118+
export default type_check;

src/semantics/type-checker.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,10 @@ export class TypeChecker {
216216
} else if (
217217
node.kind === ast.SyntaxKind.Port ||
218218
node.kind === ast.SyntaxKind.Event ||
219+
node.kind === ast.SyntaxKind.EventParameter ||
219220
node.kind === ast.SyntaxKind.ExternDeclaration ||
220221
node.kind === ast.SyntaxKind.EnumDefinition ||
222+
node.kind === ast.SyntaxKind.FunctionParameter ||
221223
node.kind === ast.SyntaxKind.Namespace ||
222224
node.kind === ast.SyntaxKind.Instance ||
223225
node.kind === ast.SyntaxKind.VariableDefinition ||
@@ -308,6 +310,11 @@ export class TypeChecker {
308310
const typeSymbol = this.symbolOfNode(definition.type);
309311
if (!typeSymbol) return ERROR_TYPE;
310312
return this.typeOfSymbol(typeSymbol);
313+
} else if (declaration.kind === ast.SyntaxKind.EventParameter) {
314+
const definition = declaration as ast.EventParameter;
315+
const typeSymbol = this.symbolOfNode(definition.type);
316+
if (!typeSymbol) return ERROR_TYPE;
317+
return this.typeOfSymbol(typeSymbol);
311318
} else if (declaration.kind === ast.SyntaxKind.EnumDefinition) {
312319
const definition = declaration as ast.EnumDefinition;
313320
return { kind: TypeKind.Enum, declaration: definition, name: definition.name.text };

src/util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ export function isCompound(node: ast.AnyAstNode): node is ast.Compound {
148148
return node.kind === ast.SyntaxKind.Compound;
149149
}
150150

151+
export function isDollarsLiteral(node: ast.AnyAstNode): node is ast.DollarsLiteral {
152+
return node.kind === ast.SyntaxKind.DollarLiteral;
153+
}
154+
151155
export function isEvent(node: ast.AnyAstNode): node is ast.Event {
152156
return node.kind === ast.SyntaxKind.Event;
153157
}

0 commit comments

Comments
 (0)