Skip to content

Commit f40091b

Browse files
committed
Add instance type resolution for FindFirstChild/FindFirstAncestor
Closes #18
1 parent bb5edec commit f40091b

File tree

4 files changed

+64
-0
lines changed

4 files changed

+64
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
99
### Added
1010

1111
- Player will now have `PlayerGui`, `Backpack`, `StarterGear` and `PlayerScripts` children, with the relevant Starter instances copied into it (StarterGui, StarterPack, PlayerScripts)
12+
- `Instance:FindFirstChild("name")` and `Instance:FindFirstAncestor("name")` will now correctly resolve to the relevant instance type if found. This allows the type checker to correctly resolve children/parents etc.
1213

1314
### Changed
1415

src/LuauExt.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,55 @@ Luau::TypeId makeLazyInstanceType(Luau::TypeArena& arena, const Luau::ScopePtr&
7878
{
7979
ctv->props[child->name] = Luau::makeProperty(makeLazyInstanceType(arena, globalScope, child, typeId));
8080
}
81+
82+
// Add FindFirstAncestor and FindFirstChild
83+
if (auto instanceType = getTypeIdForClass(globalScope, "Instance"))
84+
{
85+
auto findFirstAncestorFunction = Luau::makeFunction(arena, typeId, {Luau::getSingletonTypes().stringType}, {"name"}, {*instanceType});
86+
Luau::attachMagicFunction(findFirstAncestorFunction,
87+
[node](Luau::TypeChecker& typeChecker, const Luau::ScopePtr& scope, const Luau::AstExprCall& expr,
88+
Luau::ExprResult<Luau::TypePackId> exprResult) -> std::optional<Luau::ExprResult<Luau::TypePackId>>
89+
{
90+
if (expr.args.size < 1)
91+
return std::nullopt;
92+
93+
auto str = expr.args.data[0]->as<Luau::AstExprConstantString>();
94+
if (!str)
95+
return std::nullopt;
96+
97+
// This is a O(n) search, not great!
98+
if (auto ancestor = node->findAncestor(std::string(str->value.data, str->value.size)))
99+
{
100+
return Luau::ExprResult<Luau::TypePackId>{
101+
typeChecker.globalTypes.addTypePack({makeLazyInstanceType(typeChecker.globalTypes, scope, *ancestor, std::nullopt)})};
102+
}
103+
104+
return std::nullopt;
105+
});
106+
ctv->props["FindFirstAncestor"] = Luau::makeProperty(findFirstAncestorFunction, "@roblox/globaltype/Instance.FindFirstAncestor");
107+
108+
auto findFirstChildFunction = Luau::makeFunction(arena, typeId, {Luau::getSingletonTypes().stringType}, {"name"}, {*instanceType});
109+
Luau::attachMagicFunction(findFirstChildFunction,
110+
[node, typeId](Luau::TypeChecker& typeChecker, const Luau::ScopePtr& scope, const Luau::AstExprCall& expr,
111+
Luau::ExprResult<Luau::TypePackId> exprResult) -> std::optional<Luau::ExprResult<Luau::TypePackId>>
112+
{
113+
if (expr.args.size < 1)
114+
return std::nullopt;
115+
116+
auto str = expr.args.data[0]->as<Luau::AstExprConstantString>();
117+
if (!str)
118+
return std::nullopt;
119+
120+
if (auto child = node->findChild(std::string(str->value.data, str->value.size)))
121+
{
122+
return Luau::ExprResult<Luau::TypePackId>{
123+
typeChecker.globalTypes.addTypePack({makeLazyInstanceType(typeChecker.globalTypes, scope, *child, typeId)})};
124+
}
125+
126+
return std::nullopt;
127+
});
128+
ctv->props["FindFirstChild"] = Luau::makeProperty(findFirstChildFunction, "@roblox/globaltype/Instance.FindFirstChild");
129+
}
81130
}
82131
return typeId;
83132
};

src/Sourcemap.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ std::optional<SourceNodePtr> SourceNode::findChild(const std::string& name)
5555
return std::nullopt;
5656
}
5757

58+
std::optional<SourceNodePtr> SourceNode::findAncestor(const std::string& name)
59+
{
60+
auto current = parent;
61+
while (auto currentPtr = current.lock())
62+
{
63+
if (currentPtr->name == name)
64+
return currentPtr;
65+
current = currentPtr->parent;
66+
}
67+
return std::nullopt;
68+
}
69+
5870
static bool endsWith(std::string str, std::string suffix)
5971
{
6072
return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);

src/include/LSP/Sourcemap.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ struct SourceNode
2020
std::optional<std::filesystem::path> getScriptFilePath();
2121
Luau::SourceCode::Type sourceCodeType() const;
2222
std::optional<SourceNodePtr> findChild(const std::string& name);
23+
// O(n) search for ancestor of name
24+
std::optional<SourceNodePtr> findAncestor(const std::string& name);
2325
};
2426

2527
static void from_json(const json& j, SourceNode& p)

0 commit comments

Comments
 (0)