Skip to content

Commit 18f5fac

Browse files
committed
Add warning diagnostic for singleton Get yields
1 parent 9938c9c commit 18f5fac

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

src/Shared/diagnostics.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,11 @@ export const warnings = {
451451
"Multi-dimensional arrays are not supported as properties",
452452
suggestion("to turn off this warning, put @NonSerialized() in front of this property"),
453453
),
454+
455+
singletonGetPossibleYield: warningWithContext((text: string) => {
456+
return [
457+
`This singleton Get call ${text} may yield the containing thread and cause an error at runtime!`,
458+
suggestion("The call to get the singleton should be inside a function or method body"),
459+
];
460+
}),
454461
};

src/TSTransformer/classes/AirshipSymbolManager.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { SINGLETON_FILE_IMPORT } from "TSTransformer/classes/TransformState";
66
import { PROPERTY_SETTERS } from "TSTransformer/macros/propertyMacros";
77
import { MacroList, PropertyCallMacro } from "TSTransformer/macros/types";
88
import { skipUpwards } from "TSTransformer/util/traversal";
9+
import { findAncestorNode } from "TSTransformer/util/visitParentNodes";
910
import ts from "typescript";
11+
import { DiagnosticService } from "./DiagnosticService";
12+
import { warnings } from "Shared/diagnostics";
1013

1114
function getType(typeChecker: ts.TypeChecker, node: ts.Node) {
1215
return typeChecker.getTypeAtLocation(skipUpwards(node));
@@ -39,11 +42,29 @@ const AIRSHIP_SERIALIZE_TYPES = {
3942
AnimationCurve: "AnimationCurve",
4043
} as const;
4144

45+
function isValidSingletonFetchContext(value: ts.Node) {
46+
// Essentially anything that isn't ran during script setup
47+
return (
48+
ts.isMethodDeclaration(value) ||
49+
ts.isFunctionDeclaration(value) ||
50+
ts.isConstructorDeclaration(value) ||
51+
ts.isFunctionExpression(value) ||
52+
ts.isArrowFunction(value) ||
53+
ts.isPropertyDeclaration(value)
54+
);
55+
}
56+
4257
export const AIRSHIP_SINGLETON_MACROS = {
4358
Get: (state, node) => {
4459
const importId = state.getOrAddFileImport(SINGLETON_FILE_IMPORT, "SingletonRegistry");
4560
const Singletons_Resolve = luau.property(importId, "Resolve");
4661

62+
const ancestor = findAncestorNode(node, isValidSingletonFetchContext);
63+
64+
if (ancestor === undefined) {
65+
DiagnosticService.addDiagnostic(warnings.singletonGetPossibleYield(node, node.getText()));
66+
}
67+
4768
const functionType = state.typeChecker.getTypeAtLocation(node);
4869
if (functionType !== undefined) {
4970
return luau.call(Singletons_Resolve, [luau.string(state.typeChecker.typeToString(functionType))]);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import ts from "typescript";
2+
3+
function* visitParentNodes(node: ts.Node) {
4+
let current = node.parent;
5+
6+
do {
7+
yield current;
8+
current = current.parent;
9+
} while (current);
10+
}
11+
12+
export function findAncestorNode<T extends ts.Node>(
13+
node: ts.Node,
14+
getNode: (value: ts.Node) => value is T,
15+
breakAtNode?: (value: ts.Node) => boolean,
16+
) {
17+
// if (getNode(node)) return node;
18+
19+
for (const parentNode of visitParentNodes(node)) {
20+
if (breakAtNode?.(parentNode)) break;
21+
if (getNode(parentNode)) return parentNode;
22+
}
23+
24+
return undefined;
25+
}

0 commit comments

Comments
 (0)