1+ #include " Platform/RobloxPlatform.hpp"
2+ #include " LSP/JsonTomlSyntaxParser.hpp"
3+ #include " Luau/BuiltinDefinitions.h"
4+ #include " Luau/ConstraintSolver.h"
5+ #include " Luau/TypeInfer.h"
6+
7+ #include < filesystem>
8+ #include < queue>
9+
10+ #ifdef NEVERMORE_STRING_REQUIRE
11+
12+ struct MagicStringRequireLookup final : Luau::MagicFunction
13+ {
14+ const Luau::GlobalTypes& globals;
15+ const RobloxPlatform& platform;
16+ Luau::TypeArena& arena;
17+ SourceNodePtr node;
18+
19+ MagicStringRequireLookup (const Luau::GlobalTypes& globals, const RobloxPlatform& platform, Luau::TypeArena& arena, SourceNodePtr node)
20+ : globals(globals)
21+ , platform(platform)
22+ , arena(arena)
23+ , node(std::move(node))
24+ {
25+ }
26+
27+ std::optional<Luau::WithPredicate<Luau::TypePackId>> handleOldSolver (Luau::TypeChecker& typeChecker, const Luau::ScopePtr& scope,
28+ const Luau::AstExprCall& expr, Luau::WithPredicate<Luau::TypePackId> withPredicate) override ;
29+ bool infer (const Luau::MagicFunctionCallContext& context) override ;
30+ };
31+
32+ std::optional<Luau::WithPredicate<Luau::TypePackId>> MagicStringRequireLookup::handleOldSolver (
33+ Luau::TypeChecker& typeChecker, const Luau::ScopePtr& scope, const Luau::AstExprCall& expr, Luau::WithPredicate<Luau::TypePackId>)
34+ {
35+ if (expr.args .size < 1 )
36+ {
37+ typeChecker.reportError (Luau::TypeError{expr.args .data [0 ]->location , Luau::UnknownRequire{}});
38+ return std::nullopt ;
39+ }
40+
41+ auto str = expr.args .data [0 ]->as <Luau::AstExprConstantString>();
42+ if (!str)
43+ {
44+ typeChecker.reportError (Luau::TypeError{expr.args .data [0 ]->location , Luau::UnknownRequire{}});
45+ return std::nullopt ;
46+ }
47+
48+ auto moduleName = std::string (str->value .data , str->value .size );
49+
50+ if (node->name == moduleName)
51+ {
52+ typeChecker.reportError (Luau::TypeError{expr.args .data [0 ]->location , Luau::UnknownRequire{ moduleName }});
53+ return std::nullopt ;
54+ }
55+
56+ auto module = platform.findStringModule (moduleName);
57+ if (!module .has_value ())
58+ {
59+ typeChecker.reportError (Luau::TypeError{expr.args .data [0 ]->location , Luau::UnknownRequire{ moduleName }});
60+ return std::nullopt ;
61+ }
62+
63+ Luau::ModuleInfo moduleInfo;
64+ moduleInfo.name = module .value ()->virtualPath ;
65+
66+ return Luau::WithPredicate<Luau::TypePackId>{arena.addTypePack ({typeChecker.checkRequire (scope, moduleInfo, expr.args .data [0 ]->location )})};
67+ }
68+
69+ bool MagicStringRequireLookup::infer (const Luau::MagicFunctionCallContext& context)
70+ {
71+ // TODO: Actually like, do something here
72+ if (context.callSite ->args .size < 1 )
73+ return false ;
74+
75+ auto str = context.callSite ->args .data [0 ]->as <Luau::AstExprConstantString>();
76+ if (!str)
77+ return false ;
78+
79+ auto moduleName = std::string (str->value .data , str->value .size );
80+ auto module = platform.findStringModule (moduleName);
81+ if (!module .has_value ())
82+ {
83+ context.solver ->reportError (Luau::UnknownRequire{ moduleName }, context.callSite ->args .data [0 ]->location );
84+ return false ;
85+ }
86+
87+
88+ Luau::ModuleInfo moduleInfo;
89+ moduleInfo.name = module .value ()->virtualPath ;
90+
91+ asMutable (context.result )->ty .emplace <Luau::BoundTypePack>(context.solver ->arena ->addTypePack ({
92+ context.solver ->resolveModule (moduleInfo, context.callSite ->args .data [0 ]->location )
93+ }));
94+
95+ return true ;
96+ }
97+
98+ static void attachMagicStringRequireLookupFunction (const Luau::GlobalTypes& globals, const RobloxPlatform& platform, Luau::TypeArena& arena, const SourceNodePtr& node, Luau::TypeId lookupFuncTy)
99+ {
100+
101+ Luau::attachMagicFunction (
102+ lookupFuncTy, std::make_shared<MagicStringRequireLookup>(globals, platform, arena, node));
103+ Luau::attachTag (lookupFuncTy, kSourcemapGeneratedTag );
104+ Luau::attachTag (lookupFuncTy, " StringRequires" );
105+ Luau::attachTag (lookupFuncTy, " require" ); // Magic tag
106+ }
107+
108+ Luau::TypeId RobloxPlatform::getStringRequireType (const Luau::GlobalTypes& globals, Luau::TypeArena& arena, const SourceNodePtr& node) const
109+ {
110+ // Gets the type corresponding to the sourcemap node if it exists
111+ // Make sure to use the correct ty version (base typeChecker vs autocomplete typeChecker)
112+ if (node->stringRequireTypes .find (&globals) != node->stringRequireTypes .end ())
113+ return node->stringRequireTypes .at (&globals);
114+
115+ // TODO: Memory safety for RobloxPlatform this
116+
117+ Luau::LazyType lazyTypeValue (
118+ [&globals, this , &arena, node](Luau::LazyType& lazyTypeValue) -> void
119+ {
120+ // Check if the lazy type value already has an unwrapped type
121+ if (lazyTypeValue.unwrapped .load ())
122+ return ;
123+
124+ // Handle if the node is no longer valid
125+ if (!node)
126+ {
127+ lazyTypeValue.unwrapped = globals.builtinTypes ->anyType ;
128+ return ;
129+ }
130+
131+ // TODO: Resolve name to lazy instance
132+ // Or type union
133+ Luau::TypePackId argTypes = arena.addTypePack ({ globals.builtinTypes ->stringType });
134+ Luau::TypePackId retTypes = arena.addTypePack ({ globals.builtinTypes ->anyType }); // This should be overriden by the type checker
135+ Luau::FunctionType functionCtv (argTypes, retTypes);
136+
137+ auto typeId = arena.addType (std::move (functionCtv));
138+ attachMagicStringRequireLookupFunction (globals, *this , arena, node, typeId);
139+
140+ lazyTypeValue.unwrapped = typeId;
141+ return ;
142+ });
143+
144+ auto ty = arena.addType (std::move (lazyTypeValue));
145+ node->stringRequireTypes .insert_or_assign (&globals, ty);
146+
147+ return ty;
148+ }
149+
150+ std::optional<SourceNodePtr> RobloxPlatform::findStringModule (const std::string& moduleName) const
151+ {
152+ // TODO: Use "node_modules" as a project scope and handle duplications
153+ auto result = this ->moduleNameToSourceNode .find (moduleName);
154+ if (result != this ->moduleNameToSourceNode .end ())
155+ return result->second ;
156+
157+ return std::nullopt ;
158+ }
159+
160+ std::optional<Luau::SourceCode> RobloxPlatform::resolveToVirtualSourceCode (const Luau::ModuleName& name) const
161+ {
162+ if (!isVirtualPath (name))
163+ {
164+ return std::nullopt ;
165+ }
166+
167+ auto sourceNode = getSourceNodeFromVirtualPath (name);
168+ if (!sourceNode || !sourceNode.value ()->isVirtualNevermoreLoader )
169+ {
170+ return std::nullopt ;
171+ }
172+
173+ std::string source = R"lua(
174+ --!strict
175+
176+ local loader = {}
177+
178+ function loader.load(thisScript: ModuleScript): typeof(StringRequire)
179+ return nil :: never
180+ end
181+
182+ return loader
183+ )lua" ;
184+
185+ return Luau::SourceCode {
186+ source,
187+ Luau::SourceCode::Type::Module,
188+ };
189+ }
190+
191+ #endif
0 commit comments