1 |
|
2 | import {
|
3 | DiagnosticCategory,
|
4 | Node,
|
5 | Symbol,
|
6 | Type,
|
7 | TypeFlags,
|
8 | TypeGuards,
|
9 | ts,
|
10 | Identifier,
|
11 | } from 'ts-simple-ast';
|
12 |
|
13 | import { CompilerDiagnostic } from './CompilerDiagnostic';
|
14 | import { DiagnosticCode } from './DiagnosticCode';
|
15 | import { Globals, Libs, LibAliases } from './symbols';
|
16 |
|
17 | import * as typeUtils from './typeUtils';
|
18 |
|
19 | export class Context {
|
20 | public readonly diagnostics: ts.Diagnostic[] = [];
|
21 |
|
22 | constructor(
|
23 | private readonly globals: Globals,
|
24 | private readonly libs: Libs,
|
25 | private readonly libAliases: LibAliases,
|
26 | ) {}
|
27 |
|
28 | public reportError(node: Node, message: string, code: DiagnosticCode): void {
|
29 | this.diagnostics.push(
|
30 | new CompilerDiagnostic(node, message, code, DiagnosticCategory.Error),
|
31 | );
|
32 | }
|
33 |
|
34 | public reportWarning(
|
35 | node: Node,
|
36 | message: string,
|
37 | code: DiagnosticCode,
|
38 | ): void {
|
39 | this.diagnostics.push(
|
40 | new CompilerDiagnostic(node, message, code, DiagnosticCategory.Warning),
|
41 | );
|
42 | }
|
43 |
|
44 | public reportUnsupported(node: Node): void {
|
45 | this.reportError(
|
46 | node,
|
47 | 'Unsupported syntax',
|
48 | DiagnosticCode.UNSUPPORTED_SYNTAX,
|
49 | );
|
50 | }
|
51 |
|
52 | public reportTypeError(node: Node): void {
|
53 | this.reportError(
|
54 | node,
|
55 | 'Could not infer type. Please add an explicit type annotation.',
|
56 | DiagnosticCode.UNKNOWN_TYPE,
|
57 | );
|
58 | }
|
59 |
|
60 | public reportTypeWarning(node: Node): void {
|
61 | this.reportWarning(
|
62 | node,
|
63 | 'Could not infer type. Deoptimized implementation will be used. Add an explicit type annotation ' +
|
64 | 'to optimize the output.',
|
65 | DiagnosticCode.UNKNOWN_TYPE,
|
66 | );
|
67 | }
|
68 |
|
69 | public getType(node: Node, required: boolean = false): Type | undefined {
|
70 | let type = this.getNotAnyType(node.getType());
|
71 |
|
72 | if (type == null && TypeGuards.isExpression(node)) {
|
73 | type = this.getNotAnyType(node.getContextualType());
|
74 | }
|
75 |
|
76 | if (type == null) {
|
77 | if (required) {
|
78 | this.reportTypeError(node);
|
79 | } else {
|
80 | this.reportTypeWarning(node);
|
81 | }
|
82 | }
|
83 |
|
84 | return type;
|
85 | }
|
86 |
|
87 | public getTypeOfSymbol(
|
88 | symbol: Symbol | undefined,
|
89 | node: Node,
|
90 | required: boolean = false,
|
91 | ): Type | undefined {
|
92 | if (symbol == null) {
|
93 | return undefined;
|
94 | }
|
95 |
|
96 | const type = this.getNotAnyType(symbol.getTypeAtLocation(node));
|
97 | if (type == null) {
|
98 | if (required) {
|
99 | this.reportTypeError(node);
|
100 | } else {
|
101 | this.reportTypeWarning(node);
|
102 | }
|
103 | }
|
104 |
|
105 | return type;
|
106 | }
|
107 |
|
108 | public getSymbol(node: Node, required: boolean = false): Symbol | undefined {
|
109 | const symbol = node.getSymbol();
|
110 | if (symbol == null) {
|
111 | const message = 'Could not determine source symbol.';
|
112 | if (required) {
|
113 | this.reportError(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
|
114 | } else {
|
115 | this.reportWarning(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
|
116 | }
|
117 |
|
118 | return undefined;
|
119 | }
|
120 |
|
121 | const aliased = symbol.getAliasedSymbol();
|
122 | if (aliased != null) {
|
123 | return aliased;
|
124 | }
|
125 |
|
126 | return symbol;
|
127 | }
|
128 |
|
129 | public getSymbolForType(
|
130 | node: Node,
|
131 | type: Type | undefined,
|
132 | required: boolean = false,
|
133 | ): Symbol | undefined {
|
134 | if (type == null) {
|
135 | return undefined;
|
136 | }
|
137 |
|
138 | const symbol = type.getSymbol();
|
139 | if (symbol == null) {
|
140 | const message = `Could not determine source symbol for type: ${type.getText()}.`;
|
141 | if (required) {
|
142 | this.reportError(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
|
143 | } else {
|
144 | this.reportWarning(node, message, DiagnosticCode.UNKNOWN_SYMBOL);
|
145 | }
|
146 |
|
147 | return undefined;
|
148 | }
|
149 |
|
150 | const aliased = symbol.getAliasedSymbol();
|
151 | if (aliased != null) {
|
152 | return aliased;
|
153 | }
|
154 |
|
155 | return symbol;
|
156 | }
|
157 |
|
158 | public assertUnreachable(value: never): never {
|
159 | throw new Error('Should not be reached.');
|
160 | }
|
161 |
|
162 | public assertNotNull<T>(value: T | undefined | null): T {
|
163 | if (value == null) {
|
164 | throw new Error('Something went wrong. Unexpected null.');
|
165 | }
|
166 |
|
167 | return value;
|
168 | }
|
169 |
|
170 | public isOnlyGlobal(
|
171 | node: Node,
|
172 | type: Type | undefined,
|
173 | name: keyof Globals,
|
174 | ): boolean {
|
175 | return (
|
176 | this.isSymbolic(type) &&
|
177 | this.isGlobalSymbol(node, this.getSymbolForType(node, type), name)
|
178 | );
|
179 | }
|
180 |
|
181 | public isGlobal(
|
182 | node: Node,
|
183 | type: Type | undefined,
|
184 | name: keyof Globals,
|
185 | ): boolean {
|
186 | return (
|
187 | this.isSymbolic(type) &&
|
188 | this.isGlobalSymbol(node, this.getSymbolForType(node, type), name)
|
189 | );
|
190 | }
|
191 |
|
192 | public isGlobalSymbol(
|
193 | node: Node,
|
194 | symbol: Symbol | undefined,
|
195 | name: keyof Globals,
|
196 | ): boolean {
|
197 | return symbol === this.globals[name];
|
198 | }
|
199 |
|
200 | public isOnlyLib(
|
201 | node: Node,
|
202 | type: Type | undefined,
|
203 | name: keyof Libs,
|
204 | ): boolean {
|
205 | return (
|
206 | this.isSymbolic(type) &&
|
207 | this.isLibSymbol(node, this.getSymbolForType(node, type), name)
|
208 | );
|
209 | }
|
210 |
|
211 | public isLibSymbol(
|
212 | node: Node,
|
213 | symbol: Symbol | undefined,
|
214 | name: keyof Libs,
|
215 | ): boolean {
|
216 | return symbol === this.libs[name];
|
217 | }
|
218 |
|
219 | public isLibAlias(
|
220 | identifier: Identifier | undefined,
|
221 | name: keyof LibAliases,
|
222 | ): boolean {
|
223 | if (identifier == null) {
|
224 | return false;
|
225 | }
|
226 |
|
227 | return this.libAliases[name].has(identifier);
|
228 | }
|
229 |
|
230 | private isSymbolic(type: Type | undefined): boolean {
|
231 | return (
|
232 | type == null ||
|
233 | (!typeUtils.isLiteral(type) &&
|
234 | !typeUtils.isPrimitive(type) &&
|
235 | !typeUtils.isTuple(type) &&
|
236 | !typeUtils.isUnion(type) &&
|
237 | !typeUtils.isIntersection(type))
|
238 | );
|
239 | }
|
240 |
|
241 | private getNotAnyType(type: Type | undefined): Type | undefined {
|
242 |
|
243 | if (type == null || (type.getFlags() & TypeFlags.Any) !== 0) {
|
244 | return undefined;
|
245 | }
|
246 |
|
247 | return type;
|
248 | }
|
249 | }
|