luaut-parser 3.1.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,6 +160,55 @@ brings in names that are types and nothing else: unlike TypeScript, using one
160
160
  as a value is an error, and only type positions — `typeof A` included — may
161
161
  name it. Compiled code keeps no trace of it.
162
162
 
163
+ **Array and string methods** — an array and a string answer to methods
164
+ written with `:`, the way JavaScript writes them:
165
+
166
+ ```luau
167
+ const long = names:filter(function(n) return #n > 3 end):map(string.upper)
168
+ const first = names:find(function(n) return n:startsWith("A") end)
169
+ print(names:join(", "), text:trim(), text:replaceAll(",", ";"))
170
+ ```
171
+
172
+ Which methods those are is not the language's business. The analyzer looks for
173
+ two types by name — `ArrayMethods<T>` and `StringMethods` — and reads an
174
+ array's or a string's members out of whichever type library declared them;
175
+ without such a library an array has no methods at all.
176
+
177
+ Running them is that library's business too. A library points at a JavaScript
178
+ module in its package.json, and the compiler asks it what a call becomes:
179
+
180
+ ```json
181
+ "luaut": { "types": "index.d.luaut", "lowering": "lowering.mjs" }
182
+ ```
183
+
184
+ ```ts
185
+ import type { LoweringPlugin } from "luaut-parser" // the contract, declared here
186
+
187
+ const plugin: LoweringPlugin = {
188
+ runtime: { array: "local __NAME__ = {}\nfunction __NAME__.filter(t, test) ... end" },
189
+ methodCall({ method, receiver, use }) {
190
+ if (receiver?.kind === "array" && method === "filter") {
191
+ return { callee: `${use("array")}.filter` }
192
+ }
193
+ return undefined
194
+ },
195
+ }
196
+ export default plugin
197
+ ```
198
+
199
+ `receiver` is the luaut type the analyzer worked out, `use(key)` gives the
200
+ local name that table got — emitted once, at the top of the output, only if a
201
+ call needed it — and the receiver is passed as the call's first argument. An
202
+ answer of `undefined` leaves an ordinary Luau method call, which is what
203
+ `text:upper()` wants, since a string already answers to it.
204
+
205
+ The compiler lowers the language and nothing else: `filter` appears nowhere in
206
+ it.
207
+
208
+ `@luaut/lua` ships the JavaScript-shaped set; there, indices are Luau's (the
209
+ first element is 1, `indexOf` answers `nil` rather than -1) and `push`, `pop`,
210
+ `shift`, `unshift`, `sort` and `reverse` change the array they are called on.
211
+
163
212
  **Optionality** — there is no `T?` shorthand. `?` in type position always
164
213
  belongs to a conditional type, and in expression position to a ternary or an
165
214
  optional chain.
package/dist/index.cjs CHANGED
@@ -4824,8 +4824,8 @@ var TypeAnalyzer = class {
4824
4824
  /** Recursion guard for `preVisitBody`. */
4825
4825
  preVisitDepth = 0;
4826
4826
  run() {
4827
- this.registerAliasDefs(preludeProgram().body);
4828
- for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
4827
+ this.registerAliasDefs(preludeProgram().body, true);
4828
+ for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
4829
4829
  this.registerAliasDefs(this.program.body);
4830
4830
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
4831
4831
  this.registerImportedTypes();
@@ -4903,10 +4903,28 @@ var TypeAnalyzer = class {
4903
4903
  }
4904
4904
  }
4905
4905
  }
4906
- registerAliasDefs(block) {
4906
+ /** `layering` is on for the prelude and for definitions files: a second
4907
+ * library that declares an alias already declared *adds* to it, the way a
4908
+ * second `declare` of a table's name does, so `@luaut/roblox` can give
4909
+ * `StringMethods` Luau's `split` without restating Lua's. The file being
4910
+ * analysed is not a layer: its own alias replaces what the libraries
4911
+ * gave, which is how a project opts out of a set. */
4912
+ registerAliasDefs(block, layering = false) {
4907
4913
  for (const stmt of block.statements) {
4908
4914
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
4909
- if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4915
+ if (alias) {
4916
+ const previous = layering ? this.aliasDefs.get(alias.name.name) : void 0;
4917
+ const node = previous && !previous.class ? {
4918
+ type: "IntersectionTypeNode",
4919
+ types: [previous.node, alias.definition],
4920
+ line: alias.definition.line,
4921
+ column: alias.definition.column
4922
+ } : alias.definition;
4923
+ this.aliasDefs.set(alias.name.name, {
4924
+ params: previous && !previous.class && previous.params.length ? previous.params : alias.generics,
4925
+ node
4926
+ });
4927
+ }
4910
4928
  if (stmt.type === "DeclareClassStatement") {
4911
4929
  this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4912
4930
  }
@@ -6171,13 +6189,15 @@ var TypeAnalyzer = class {
6171
6189
  const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
6172
6190
  if (!objects.length) return;
6173
6191
  for (const field of e.fields) {
6174
- if (field.type !== "TableFieldNamed") continue;
6175
- const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
6192
+ if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
6193
+ const key = field.type === "TableFieldShorthand" ? field.name.name : field.key.type === "Identifier" ? field.key.name : field.key.value;
6176
6194
  const types = objects.flatMap((o) => {
6177
6195
  const property = o.properties.get(key);
6178
6196
  return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
6179
6197
  });
6180
- if (types.length) this.applyContext(field.value, union(types));
6198
+ if (types.length) {
6199
+ this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
6200
+ }
6181
6201
  }
6182
6202
  }
6183
6203
  /** The members of an expected type worth matching a literal against:
@@ -6976,6 +6996,25 @@ var TypeAnalyzer = class {
6976
6996
  this.resolvingAliases.delete(t.name);
6977
6997
  }
6978
6998
  }
6999
+ /** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
7000
+ * They are written in the prelude as `ArrayMethods<T>` and
7001
+ * `StringMethods`, so a file (or a type library) that declares one of
7002
+ * those names again replaces the whole set, and nothing here is a special
7003
+ * case in the analyzer. The build lowers each call to a plain function. */
7004
+ builtInMethod(t, name) {
7005
+ const element = t.kind === "array" ? t.element : t.kind === "tuple" ? union(t.elements) : void 0;
7006
+ const methodTable = element !== void 0 ? "ArrayMethods" : t.kind === "primitive" && t.name === "string" || t.kind === "literal" && t.base === "string" ? "StringMethods" : void 0;
7007
+ const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
7008
+ if (!def || def.class) return void 0;
7009
+ const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
7010
+ const parts = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
7011
+ for (let i = parts.length - 1; i >= 0; i--) {
7012
+ const part = parts[i];
7013
+ const property = part.kind === "object" ? part.properties.get(name) : void 0;
7014
+ if (property) return property.type;
7015
+ }
7016
+ return void 0;
7017
+ }
6979
7018
  propertyType(raw, name) {
6980
7019
  const t = this.deferredAccess(this.expand(raw));
6981
7020
  if (t.kind === "object") {
@@ -6983,6 +7022,8 @@ var TypeAnalyzer = class {
6983
7022
  if (p) return p.optional ? optional(p.type) : p.type;
6984
7023
  if (t.indexer) return t.indexer.value;
6985
7024
  }
7025
+ const built = this.builtInMethod(t, name);
7026
+ if (built) return built;
6986
7027
  if (t.kind === "union") return union(t.types.map((m) => this.propertyType(m, name)));
6987
7028
  if (t.kind === "intersection") {
6988
7029
  const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
@@ -7336,7 +7377,21 @@ var TypeAnalyzer = class {
7336
7377
  }
7337
7378
  }
7338
7379
  if (asConst && !hadSpread) return tuple(elems);
7339
- return arrayOf(elems.length ? union(elems.map((t) => asConst ? t : widen(t))) : unknownType);
7380
+ return arrayOf(elems.length ? union(elems.map((t, i) => {
7381
+ const element = expr.elements[i];
7382
+ return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
7383
+ })) : unknownType);
7384
+ }
7385
+ /** A literal written inside a fresh table or array widens — `{ n = 1 }` is
7386
+ * `{ n: number }` — unless the surroundings said a literal belongs there.
7387
+ * `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
7388
+ * string literals, exactly as TypeScript's contextual typing does, and
7389
+ * goes on widening to `string` when the parameter only says `string`.
7390
+ * The context was recorded by `applyContext` before the value was
7391
+ * inferred, so this is a lookup rather than a second pass. */
7392
+ widenUnlessAsked(value, at) {
7393
+ const wanted = this.expectedTypeOf.get(at);
7394
+ return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
7340
7395
  }
7341
7396
  inferObject(expr, env, asConst) {
7342
7397
  const entries = [];
@@ -7344,16 +7399,24 @@ var TypeAnalyzer = class {
7344
7399
  for (const field of expr.fields) {
7345
7400
  if (field.type === "TableFieldNamed") {
7346
7401
  const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
7347
- const v = asConst ? this.inferAsConst(field.value, env) : widen(this.infer(field.value, env));
7402
+ const v = asConst ? this.inferAsConst(field.value, env) : this.widenUnlessAsked(this.infer(field.value, env), field.value);
7348
7403
  entries.push([key, { type: v, optional: false, readonly: asConst }]);
7349
7404
  } else if (field.type === "TableFieldShorthand") {
7350
7405
  const v = this.infer(field.name, env);
7351
- entries.push([field.name.name, { type: asConst ? v : widen(v), optional: false, readonly: asConst }]);
7406
+ entries.push([field.name.name, {
7407
+ type: asConst ? v : this.widenUnlessAsked(v, field.name),
7408
+ optional: false,
7409
+ readonly: asConst
7410
+ }]);
7352
7411
  } else if (field.type === "TableFieldComputed") {
7353
7412
  const k = this.infer(field.key, env);
7354
7413
  const v = this.infer(field.value, env);
7355
7414
  if (k.kind === "literal" && typeof k.value === "string") {
7356
- entries.push([k.value, { type: asConst ? v : widen(v), optional: false, readonly: asConst }]);
7415
+ entries.push([k.value, {
7416
+ type: asConst ? v : this.widenUnlessAsked(v, field.value),
7417
+ optional: false,
7418
+ readonly: asConst
7419
+ }]);
7357
7420
  } else {
7358
7421
  indexer = mergeIndexer(indexer, { key: widen(k), value: asConst ? v : widen(v) });
7359
7422
  }
@@ -8178,6 +8241,7 @@ function offsetPosition(source, offset) {
8178
8241
  var import_node_path2 = require("path");
8179
8242
  function resolveTypeLibraries(config, host = nodeHost) {
8180
8243
  const files = [];
8244
+ const lowerings = [];
8181
8245
  const problems = [];
8182
8246
  const loaded = /* @__PURE__ */ new Set();
8183
8247
  const addFile = (file) => {
@@ -8195,6 +8259,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
8195
8259
  if (found) addPackage(found.directory, found.file, visiting);
8196
8260
  }
8197
8261
  addFile(entryFile);
8262
+ const lowering = loweringModule(directory, host, problems, config);
8263
+ if (lowering) lowerings.push(lowering);
8198
8264
  };
8199
8265
  for (const entry of config.types) {
8200
8266
  const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
@@ -8221,7 +8287,19 @@ function resolveTypeLibraries(config, host = nodeHost) {
8221
8287
  });
8222
8288
  }
8223
8289
  }
8224
- return { files, problems };
8290
+ return { files, lowerings, problems };
8291
+ }
8292
+ function loweringModule(directory, host, problems, config) {
8293
+ const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
8294
+ const declared = manifest?.luaut?.lowering;
8295
+ if (typeof declared !== "string") return void 0;
8296
+ const from = typeof manifest?.name === "string" ? manifest.name : directory;
8297
+ const file = (0, import_node_path2.resolve)(directory, declared);
8298
+ if (host.readFile(file) === void 0) {
8299
+ problems.push({ file: config.path, message: `'${from}' names a lowering module '${declared}', which is not there` });
8300
+ return void 0;
8301
+ }
8302
+ return { file, from };
8225
8303
  }
8226
8304
  var ENTRY_FILE = "index.d.luaut";
8227
8305
  function packageEntry(directory, host) {
package/dist/index.d.cts CHANGED
@@ -1309,6 +1309,13 @@ resolveModule?: (specifier: string) => ModuleExports | undefined): ModuleExports
1309
1309
  *
1310
1310
  * What a runtime provides — `print`, `string`, `game` — is not here: that is a
1311
1311
  * type library's job (`@luaut/lua`, `@luaut/roblox`).
1312
+ *
1313
+ * The methods an array and a string answer to — `names:filter(f)`,
1314
+ * `text:trim()` — are a library's too. `propertyType` reads them from types
1315
+ * named `ArrayMethods<T>` and `StringMethods`, whichever library declares
1316
+ * those; the library also says which of them the compiler must emit code for
1317
+ * (`luaut.methods` in its package.json). Nothing about `filter` is written
1318
+ * into the analyzer.
1312
1319
  */
1313
1320
  declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...unknown) -> infer R ? R : never\ntype Parameters<T> = T extends (...infer P) -> unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n";
1314
1321
 
@@ -1367,10 +1374,93 @@ declare function stripJsonComments(text: string): string;
1367
1374
  interface TypeLibraries {
1368
1375
  /** Definitions files, dependencies before what depends on them. */
1369
1376
  readonly files: readonly string[];
1377
+ /** Lowering modules the libraries ship, in the same order. */
1378
+ readonly lowerings: readonly LoweringModule[];
1370
1379
  readonly problems: readonly ConfigProblem[];
1371
1380
  }
1381
+ /** A library's own lowering: JavaScript the compiler loads and asks what a
1382
+ * call written against this library's types should become.
1383
+ *
1384
+ * The library declares the *types* in its definitions file; this is the other
1385
+ * half. `names:filter(f)` is a call to a function only because `@luaut/lua`
1386
+ * says so and ships the Luau behind it — the compiler knows how to ask, and
1387
+ * nothing about `filter`.
1388
+ *
1389
+ * `luaut.lowering` in the package.json names the module; what it must export
1390
+ * is the compiler's business (see luaut-build's `LoweringPlugin`). */
1391
+ interface LoweringModule {
1392
+ /** The JavaScript module to load. */
1393
+ readonly file: string;
1394
+ /** The package it came from, for reporting. */
1395
+ readonly from: string;
1396
+ }
1372
1397
  declare function resolveTypeLibraries(config: LuautConfig, host?: ProjectHost): TypeLibraries;
1373
1398
 
1399
+ /**
1400
+ * The contract between a type library and the compiler.
1401
+ *
1402
+ * A library's definitions file says what a value *is*; when what it gives is
1403
+ * not something the value already answers to, the library must also say how
1404
+ * it runs. `names:filter(f)` is a call to a function because `@luaut/lua`
1405
+ * declares the method and ships the Luau behind it — the compiler lowers the
1406
+ * language (`import`, `export`, `?.`, `a ? b : c`, destructuring, spreads)
1407
+ * and asks a library about everything else.
1408
+ *
1409
+ * These types are declarations only: nothing here runs, and the parser never
1410
+ * loads a lowering module. They live here so a library can be written in
1411
+ * TypeScript against the same contract the compiler implements, without
1412
+ * depending on the compiler.
1413
+ *
1414
+ * // lowering.ts, in a type library
1415
+ * import type { LoweringPlugin } from "luaut-parser"
1416
+ *
1417
+ * const plugin: LoweringPlugin = {
1418
+ * runtime: { array: "local __NAME__ = {}\n..." },
1419
+ * methodCall({ method, receiver, use }) {
1420
+ * if (receiver?.kind === "array" && method === "filter") {
1421
+ * return { callee: `${use("array")}.filter` }
1422
+ * }
1423
+ * return undefined
1424
+ * },
1425
+ * }
1426
+ * export default plugin
1427
+ */
1428
+
1429
+ interface LoweringPlugin {
1430
+ /** Luau the plugin needs in the output, by a key it chooses. Each is a
1431
+ * file's worth of source with `__NAME__` standing for the local the
1432
+ * compiler gives it, and each is emitted once, at the top of the output,
1433
+ * only if `use` asked for it:
1434
+ *
1435
+ * local __NAME__ = {}
1436
+ * function __NAME__.filter(t, test) ... end
1437
+ */
1438
+ readonly runtime?: Readonly<Record<string, string>>;
1439
+ /** What `receiver:method(...)` becomes. `undefined` leaves a plain Luau
1440
+ * method call, which is what a value that answers to the method itself
1441
+ * wants — `text:upper()` reaches Lua's own. */
1442
+ methodCall?(call: MethodCall): MethodLowering | undefined;
1443
+ }
1444
+ interface MethodCall {
1445
+ /** The name written after `:`. */
1446
+ readonly method: string;
1447
+ /** The receiver's type, as the analyzer worked it out. `undefined` when
1448
+ * nothing typed it, where a plugin should decline rather than guess. */
1449
+ readonly receiver: Type | undefined;
1450
+ /** How many arguments were written. */
1451
+ readonly argumentCount: number;
1452
+ /** The local name the output gives one of `runtime`'s entries, emitting
1453
+ * it if this is the first call that needed it. */
1454
+ use(runtime: string): string;
1455
+ }
1456
+ interface MethodLowering {
1457
+ /** What to call instead: a name, or a `table.member` path — usually built
1458
+ * from `use(...)`. */
1459
+ readonly callee: string;
1460
+ /** Pass the receiver as the first argument. Default: yes. */
1461
+ readonly passReceiver?: boolean;
1462
+ }
1463
+
1374
1464
  /** Every file `specifier` could mean from `fromFile`, in the order they are
1375
1465
  * tried. A resolver that caches should watch all of them: creating an earlier
1376
1466
  * candidate changes what the import means. */
@@ -1417,4 +1507,4 @@ declare const luautparser: {
1417
1507
  readonly analyzeTypes: typeof analyzeTypes;
1418
1508
  };
1419
1509
 
1420
- export { type AnalyzeTypesOptions, type AnyType, type ArrayExpression, type ArrayPattern, type ArrayPatternElement, type ArrayType, type ArrayTypeNode, type AsConstExpression, type AssignmentStatement, type BaseNode, type BaseToken, type BinaryExpression, BinaryOperators, type Binding, type BindingId, type BindingKind, type BindingTarget, type Block, type BooleanLiteral, type BreakStatement, CONFIG_FILE_NAMES, type CallExpression, type CallStatement, type ClassInfo, type CompoundAssignmentStatement, type ConditionalType, type ConditionalTypeNode, type ConfigLookup, type ConfigProblem, type ContinueStatement, type DeclareClassStatement, type DeclareStatement, type DifferenceType, type DifferenceTypeNode, type Directive, type DirectiveKind, type DirectiveOutcome, type Directives, type DoStatement, type EOFToken, type ErrorExpression, type ErrorStatement, type ExportAllStatement, type ExportDefaultStatement, type ExportNamedStatement, type ExportSpecifier, type ExportStatement, type ExportTypeAliasStatement, type ExportedType, type Expression, type FunctionBody, type FunctionDeclaration, type FunctionDeclarationStatement, type FunctionExpression, type FunctionName, type FunctionParam, type FunctionParameter, type FunctionSignature, type FunctionType, type FunctionTypeNode, type FunctionTypeParameter, type GenericForStatement, type GenericRefType, type GenericTypeParameter, type Identifier, type IdentifierPattern, type IdentifierToken, type IfClause, type IfElseExpression, type IfStatement, type ImportSpecifier, type ImportStatement, type IndexExpression, type IndexedAccessType, type IndexedAccessTypeNode, type InferType, type InferTypeNode, type InterpolatedStringExpression, type InterpolatedStringPart, type InterpolatedStringPart_Expression, type InterpolatedStringPart_String, type InterpolatedStringToken, type IntersectionType, type IntersectionTypeNode, type KeyofType, type KeyofTypeNode, type KeywordToken, Keywords, LexError, type LiteralToken, type LiteralType, type LuautConfig, type MappedType, type MappedTypeNode, type MemberExpression, type MethodCallExpression, type ModuleExports, type NeverType, type NilLiteral, type Node, type NumberLiteral, type NumericForStatement, type ObjectPattern, type ObjectPatternProperty, type ObjectProperty, type ObjectType, type OperatorToken, Operators, PRELUDE_SOURCE, type ParenthesizedExpression, type ParenthesizedTypeNode, ParseError, type ParserOptions, type PrimitiveName, type PrimitiveType, type Program, type ProjectHost, type PunctuatorToken, Punctuators, type RecoverResult, type RepeatStatement, type ReturnStatement, type SatisfiesExpression, type ScopeAnalysis, type ScopeDiagnostic, type SourceComment, type SourceMapNode, type SourceMapOptions, type SourceMapTypes, type SpreadElement, type Statement, type StringLiteral, type TableExpression, type TableField, type TableTypeNode, type TableTypeProperty, type TemplateLiteralType, type TemplateLiteralTypeNode, type Token, type TokenizeOptions, type TupleType, type TupleTypeNode, type Type, type TypeAliasStatement, type TypeAnalysis, type TypeAssertionExpression, type TypeDiagnostic, type TypeLibraries, type TypeLiteralBoolean, type TypeLiteralNumber, type TypeLiteralString, type TypeNode, type TypePackNode, type TypeParamType, type TypePredicate, type TypePredicateNode, type TypeReference, type TypedIdentifier, type TypeofTypeNode, UNUSED_EXPECT_ERROR, type UnaryExpression, UnaryOperators, type UnionType, type UnionTypeNode, type UnknownType, type VarargExpression, type VariableDeclaration, type VariadicTypeNode, type WhileStatement, analyzeScopes, analyzeTypes, anyType, applyDirectives, arrayOf, booleanType, bufferType, containsTypeParam, luautparser as default, difference, directivesOf, equalTypes, falsyType, findConfig, fn, formatType, getBinding, intersection, isAssignable, isClassType, isGlobal, isPossiblyFalsy, isPossiblyTruthy, isUnassignedGlobal, literal, loadConfig, luautparser, matchInfer, moduleCandidates, moduleExports, narrowExclude, narrowFalsy, narrowTo, narrowTruthy, neverType, nilType, nodeHost, numberType, objectType, optional, overlaps, parse, parseExpressionFromSource, parseTokens, parseWithRecovery, primitive, readDirectives, resolveModulePath, resolveTypeLibraries, setAliasExpander, sourceMapTypes, stringType, stripJsonComments, substitute, templateMatches, threadType, tokenize, tuple, typeParam, unify, union, unknownType, widen };
1510
+ export { type AnalyzeTypesOptions, type AnyType, type ArrayExpression, type ArrayPattern, type ArrayPatternElement, type ArrayType, type ArrayTypeNode, type AsConstExpression, type AssignmentStatement, type BaseNode, type BaseToken, type BinaryExpression, BinaryOperators, type Binding, type BindingId, type BindingKind, type BindingTarget, type Block, type BooleanLiteral, type BreakStatement, CONFIG_FILE_NAMES, type CallExpression, type CallStatement, type ClassInfo, type CompoundAssignmentStatement, type ConditionalType, type ConditionalTypeNode, type ConfigLookup, type ConfigProblem, type ContinueStatement, type DeclareClassStatement, type DeclareStatement, type DifferenceType, type DifferenceTypeNode, type Directive, type DirectiveKind, type DirectiveOutcome, type Directives, type DoStatement, type EOFToken, type ErrorExpression, type ErrorStatement, type ExportAllStatement, type ExportDefaultStatement, type ExportNamedStatement, type ExportSpecifier, type ExportStatement, type ExportTypeAliasStatement, type ExportedType, type Expression, type FunctionBody, type FunctionDeclaration, type FunctionDeclarationStatement, type FunctionExpression, type FunctionName, type FunctionParam, type FunctionParameter, type FunctionSignature, type FunctionType, type FunctionTypeNode, type FunctionTypeParameter, type GenericForStatement, type GenericRefType, type GenericTypeParameter, type Identifier, type IdentifierPattern, type IdentifierToken, type IfClause, type IfElseExpression, type IfStatement, type ImportSpecifier, type ImportStatement, type IndexExpression, type IndexedAccessType, type IndexedAccessTypeNode, type InferType, type InferTypeNode, type InterpolatedStringExpression, type InterpolatedStringPart, type InterpolatedStringPart_Expression, type InterpolatedStringPart_String, type InterpolatedStringToken, type IntersectionType, type IntersectionTypeNode, type KeyofType, type KeyofTypeNode, type KeywordToken, Keywords, LexError, type LiteralToken, type LiteralType, type LoweringModule, type LoweringPlugin, type LuautConfig, type MappedType, type MappedTypeNode, type MemberExpression, type MethodCall, type MethodCallExpression, type MethodLowering, type ModuleExports, type NeverType, type NilLiteral, type Node, type NumberLiteral, type NumericForStatement, type ObjectPattern, type ObjectPatternProperty, type ObjectProperty, type ObjectType, type OperatorToken, Operators, PRELUDE_SOURCE, type ParenthesizedExpression, type ParenthesizedTypeNode, ParseError, type ParserOptions, type PrimitiveName, type PrimitiveType, type Program, type ProjectHost, type PunctuatorToken, Punctuators, type RecoverResult, type RepeatStatement, type ReturnStatement, type SatisfiesExpression, type ScopeAnalysis, type ScopeDiagnostic, type SourceComment, type SourceMapNode, type SourceMapOptions, type SourceMapTypes, type SpreadElement, type Statement, type StringLiteral, type TableExpression, type TableField, type TableTypeNode, type TableTypeProperty, type TemplateLiteralType, type TemplateLiteralTypeNode, type Token, type TokenizeOptions, type TupleType, type TupleTypeNode, type Type, type TypeAliasStatement, type TypeAnalysis, type TypeAssertionExpression, type TypeDiagnostic, type TypeLibraries, type TypeLiteralBoolean, type TypeLiteralNumber, type TypeLiteralString, type TypeNode, type TypePackNode, type TypeParamType, type TypePredicate, type TypePredicateNode, type TypeReference, type TypedIdentifier, type TypeofTypeNode, UNUSED_EXPECT_ERROR, type UnaryExpression, UnaryOperators, type UnionType, type UnionTypeNode, type UnknownType, type VarargExpression, type VariableDeclaration, type VariadicTypeNode, type WhileStatement, analyzeScopes, analyzeTypes, anyType, applyDirectives, arrayOf, booleanType, bufferType, containsTypeParam, luautparser as default, difference, directivesOf, equalTypes, falsyType, findConfig, fn, formatType, getBinding, intersection, isAssignable, isClassType, isGlobal, isPossiblyFalsy, isPossiblyTruthy, isUnassignedGlobal, literal, loadConfig, luautparser, matchInfer, moduleCandidates, moduleExports, narrowExclude, narrowFalsy, narrowTo, narrowTruthy, neverType, nilType, nodeHost, numberType, objectType, optional, overlaps, parse, parseExpressionFromSource, parseTokens, parseWithRecovery, primitive, readDirectives, resolveModulePath, resolveTypeLibraries, setAliasExpander, sourceMapTypes, stringType, stripJsonComments, substitute, templateMatches, threadType, tokenize, tuple, typeParam, unify, union, unknownType, widen };
package/dist/index.d.ts CHANGED
@@ -1309,6 +1309,13 @@ resolveModule?: (specifier: string) => ModuleExports | undefined): ModuleExports
1309
1309
  *
1310
1310
  * What a runtime provides — `print`, `string`, `game` — is not here: that is a
1311
1311
  * type library's job (`@luaut/lua`, `@luaut/roblox`).
1312
+ *
1313
+ * The methods an array and a string answer to — `names:filter(f)`,
1314
+ * `text:trim()` — are a library's too. `propertyType` reads them from types
1315
+ * named `ArrayMethods<T>` and `StringMethods`, whichever library declares
1316
+ * those; the library also says which of them the compiler must emit code for
1317
+ * (`luaut.methods` in its package.json). Nothing about `filter` is written
1318
+ * into the analyzer.
1312
1319
  */
1313
1320
  declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...unknown) -> infer R ? R : never\ntype Parameters<T> = T extends (...infer P) -> unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n";
1314
1321
 
@@ -1367,10 +1374,93 @@ declare function stripJsonComments(text: string): string;
1367
1374
  interface TypeLibraries {
1368
1375
  /** Definitions files, dependencies before what depends on them. */
1369
1376
  readonly files: readonly string[];
1377
+ /** Lowering modules the libraries ship, in the same order. */
1378
+ readonly lowerings: readonly LoweringModule[];
1370
1379
  readonly problems: readonly ConfigProblem[];
1371
1380
  }
1381
+ /** A library's own lowering: JavaScript the compiler loads and asks what a
1382
+ * call written against this library's types should become.
1383
+ *
1384
+ * The library declares the *types* in its definitions file; this is the other
1385
+ * half. `names:filter(f)` is a call to a function only because `@luaut/lua`
1386
+ * says so and ships the Luau behind it — the compiler knows how to ask, and
1387
+ * nothing about `filter`.
1388
+ *
1389
+ * `luaut.lowering` in the package.json names the module; what it must export
1390
+ * is the compiler's business (see luaut-build's `LoweringPlugin`). */
1391
+ interface LoweringModule {
1392
+ /** The JavaScript module to load. */
1393
+ readonly file: string;
1394
+ /** The package it came from, for reporting. */
1395
+ readonly from: string;
1396
+ }
1372
1397
  declare function resolveTypeLibraries(config: LuautConfig, host?: ProjectHost): TypeLibraries;
1373
1398
 
1399
+ /**
1400
+ * The contract between a type library and the compiler.
1401
+ *
1402
+ * A library's definitions file says what a value *is*; when what it gives is
1403
+ * not something the value already answers to, the library must also say how
1404
+ * it runs. `names:filter(f)` is a call to a function because `@luaut/lua`
1405
+ * declares the method and ships the Luau behind it — the compiler lowers the
1406
+ * language (`import`, `export`, `?.`, `a ? b : c`, destructuring, spreads)
1407
+ * and asks a library about everything else.
1408
+ *
1409
+ * These types are declarations only: nothing here runs, and the parser never
1410
+ * loads a lowering module. They live here so a library can be written in
1411
+ * TypeScript against the same contract the compiler implements, without
1412
+ * depending on the compiler.
1413
+ *
1414
+ * // lowering.ts, in a type library
1415
+ * import type { LoweringPlugin } from "luaut-parser"
1416
+ *
1417
+ * const plugin: LoweringPlugin = {
1418
+ * runtime: { array: "local __NAME__ = {}\n..." },
1419
+ * methodCall({ method, receiver, use }) {
1420
+ * if (receiver?.kind === "array" && method === "filter") {
1421
+ * return { callee: `${use("array")}.filter` }
1422
+ * }
1423
+ * return undefined
1424
+ * },
1425
+ * }
1426
+ * export default plugin
1427
+ */
1428
+
1429
+ interface LoweringPlugin {
1430
+ /** Luau the plugin needs in the output, by a key it chooses. Each is a
1431
+ * file's worth of source with `__NAME__` standing for the local the
1432
+ * compiler gives it, and each is emitted once, at the top of the output,
1433
+ * only if `use` asked for it:
1434
+ *
1435
+ * local __NAME__ = {}
1436
+ * function __NAME__.filter(t, test) ... end
1437
+ */
1438
+ readonly runtime?: Readonly<Record<string, string>>;
1439
+ /** What `receiver:method(...)` becomes. `undefined` leaves a plain Luau
1440
+ * method call, which is what a value that answers to the method itself
1441
+ * wants — `text:upper()` reaches Lua's own. */
1442
+ methodCall?(call: MethodCall): MethodLowering | undefined;
1443
+ }
1444
+ interface MethodCall {
1445
+ /** The name written after `:`. */
1446
+ readonly method: string;
1447
+ /** The receiver's type, as the analyzer worked it out. `undefined` when
1448
+ * nothing typed it, where a plugin should decline rather than guess. */
1449
+ readonly receiver: Type | undefined;
1450
+ /** How many arguments were written. */
1451
+ readonly argumentCount: number;
1452
+ /** The local name the output gives one of `runtime`'s entries, emitting
1453
+ * it if this is the first call that needed it. */
1454
+ use(runtime: string): string;
1455
+ }
1456
+ interface MethodLowering {
1457
+ /** What to call instead: a name, or a `table.member` path — usually built
1458
+ * from `use(...)`. */
1459
+ readonly callee: string;
1460
+ /** Pass the receiver as the first argument. Default: yes. */
1461
+ readonly passReceiver?: boolean;
1462
+ }
1463
+
1374
1464
  /** Every file `specifier` could mean from `fromFile`, in the order they are
1375
1465
  * tried. A resolver that caches should watch all of them: creating an earlier
1376
1466
  * candidate changes what the import means. */
@@ -1417,4 +1507,4 @@ declare const luautparser: {
1417
1507
  readonly analyzeTypes: typeof analyzeTypes;
1418
1508
  };
1419
1509
 
1420
- export { type AnalyzeTypesOptions, type AnyType, type ArrayExpression, type ArrayPattern, type ArrayPatternElement, type ArrayType, type ArrayTypeNode, type AsConstExpression, type AssignmentStatement, type BaseNode, type BaseToken, type BinaryExpression, BinaryOperators, type Binding, type BindingId, type BindingKind, type BindingTarget, type Block, type BooleanLiteral, type BreakStatement, CONFIG_FILE_NAMES, type CallExpression, type CallStatement, type ClassInfo, type CompoundAssignmentStatement, type ConditionalType, type ConditionalTypeNode, type ConfigLookup, type ConfigProblem, type ContinueStatement, type DeclareClassStatement, type DeclareStatement, type DifferenceType, type DifferenceTypeNode, type Directive, type DirectiveKind, type DirectiveOutcome, type Directives, type DoStatement, type EOFToken, type ErrorExpression, type ErrorStatement, type ExportAllStatement, type ExportDefaultStatement, type ExportNamedStatement, type ExportSpecifier, type ExportStatement, type ExportTypeAliasStatement, type ExportedType, type Expression, type FunctionBody, type FunctionDeclaration, type FunctionDeclarationStatement, type FunctionExpression, type FunctionName, type FunctionParam, type FunctionParameter, type FunctionSignature, type FunctionType, type FunctionTypeNode, type FunctionTypeParameter, type GenericForStatement, type GenericRefType, type GenericTypeParameter, type Identifier, type IdentifierPattern, type IdentifierToken, type IfClause, type IfElseExpression, type IfStatement, type ImportSpecifier, type ImportStatement, type IndexExpression, type IndexedAccessType, type IndexedAccessTypeNode, type InferType, type InferTypeNode, type InterpolatedStringExpression, type InterpolatedStringPart, type InterpolatedStringPart_Expression, type InterpolatedStringPart_String, type InterpolatedStringToken, type IntersectionType, type IntersectionTypeNode, type KeyofType, type KeyofTypeNode, type KeywordToken, Keywords, LexError, type LiteralToken, type LiteralType, type LuautConfig, type MappedType, type MappedTypeNode, type MemberExpression, type MethodCallExpression, type ModuleExports, type NeverType, type NilLiteral, type Node, type NumberLiteral, type NumericForStatement, type ObjectPattern, type ObjectPatternProperty, type ObjectProperty, type ObjectType, type OperatorToken, Operators, PRELUDE_SOURCE, type ParenthesizedExpression, type ParenthesizedTypeNode, ParseError, type ParserOptions, type PrimitiveName, type PrimitiveType, type Program, type ProjectHost, type PunctuatorToken, Punctuators, type RecoverResult, type RepeatStatement, type ReturnStatement, type SatisfiesExpression, type ScopeAnalysis, type ScopeDiagnostic, type SourceComment, type SourceMapNode, type SourceMapOptions, type SourceMapTypes, type SpreadElement, type Statement, type StringLiteral, type TableExpression, type TableField, type TableTypeNode, type TableTypeProperty, type TemplateLiteralType, type TemplateLiteralTypeNode, type Token, type TokenizeOptions, type TupleType, type TupleTypeNode, type Type, type TypeAliasStatement, type TypeAnalysis, type TypeAssertionExpression, type TypeDiagnostic, type TypeLibraries, type TypeLiteralBoolean, type TypeLiteralNumber, type TypeLiteralString, type TypeNode, type TypePackNode, type TypeParamType, type TypePredicate, type TypePredicateNode, type TypeReference, type TypedIdentifier, type TypeofTypeNode, UNUSED_EXPECT_ERROR, type UnaryExpression, UnaryOperators, type UnionType, type UnionTypeNode, type UnknownType, type VarargExpression, type VariableDeclaration, type VariadicTypeNode, type WhileStatement, analyzeScopes, analyzeTypes, anyType, applyDirectives, arrayOf, booleanType, bufferType, containsTypeParam, luautparser as default, difference, directivesOf, equalTypes, falsyType, findConfig, fn, formatType, getBinding, intersection, isAssignable, isClassType, isGlobal, isPossiblyFalsy, isPossiblyTruthy, isUnassignedGlobal, literal, loadConfig, luautparser, matchInfer, moduleCandidates, moduleExports, narrowExclude, narrowFalsy, narrowTo, narrowTruthy, neverType, nilType, nodeHost, numberType, objectType, optional, overlaps, parse, parseExpressionFromSource, parseTokens, parseWithRecovery, primitive, readDirectives, resolveModulePath, resolveTypeLibraries, setAliasExpander, sourceMapTypes, stringType, stripJsonComments, substitute, templateMatches, threadType, tokenize, tuple, typeParam, unify, union, unknownType, widen };
1510
+ export { type AnalyzeTypesOptions, type AnyType, type ArrayExpression, type ArrayPattern, type ArrayPatternElement, type ArrayType, type ArrayTypeNode, type AsConstExpression, type AssignmentStatement, type BaseNode, type BaseToken, type BinaryExpression, BinaryOperators, type Binding, type BindingId, type BindingKind, type BindingTarget, type Block, type BooleanLiteral, type BreakStatement, CONFIG_FILE_NAMES, type CallExpression, type CallStatement, type ClassInfo, type CompoundAssignmentStatement, type ConditionalType, type ConditionalTypeNode, type ConfigLookup, type ConfigProblem, type ContinueStatement, type DeclareClassStatement, type DeclareStatement, type DifferenceType, type DifferenceTypeNode, type Directive, type DirectiveKind, type DirectiveOutcome, type Directives, type DoStatement, type EOFToken, type ErrorExpression, type ErrorStatement, type ExportAllStatement, type ExportDefaultStatement, type ExportNamedStatement, type ExportSpecifier, type ExportStatement, type ExportTypeAliasStatement, type ExportedType, type Expression, type FunctionBody, type FunctionDeclaration, type FunctionDeclarationStatement, type FunctionExpression, type FunctionName, type FunctionParam, type FunctionParameter, type FunctionSignature, type FunctionType, type FunctionTypeNode, type FunctionTypeParameter, type GenericForStatement, type GenericRefType, type GenericTypeParameter, type Identifier, type IdentifierPattern, type IdentifierToken, type IfClause, type IfElseExpression, type IfStatement, type ImportSpecifier, type ImportStatement, type IndexExpression, type IndexedAccessType, type IndexedAccessTypeNode, type InferType, type InferTypeNode, type InterpolatedStringExpression, type InterpolatedStringPart, type InterpolatedStringPart_Expression, type InterpolatedStringPart_String, type InterpolatedStringToken, type IntersectionType, type IntersectionTypeNode, type KeyofType, type KeyofTypeNode, type KeywordToken, Keywords, LexError, type LiteralToken, type LiteralType, type LoweringModule, type LoweringPlugin, type LuautConfig, type MappedType, type MappedTypeNode, type MemberExpression, type MethodCall, type MethodCallExpression, type MethodLowering, type ModuleExports, type NeverType, type NilLiteral, type Node, type NumberLiteral, type NumericForStatement, type ObjectPattern, type ObjectPatternProperty, type ObjectProperty, type ObjectType, type OperatorToken, Operators, PRELUDE_SOURCE, type ParenthesizedExpression, type ParenthesizedTypeNode, ParseError, type ParserOptions, type PrimitiveName, type PrimitiveType, type Program, type ProjectHost, type PunctuatorToken, Punctuators, type RecoverResult, type RepeatStatement, type ReturnStatement, type SatisfiesExpression, type ScopeAnalysis, type ScopeDiagnostic, type SourceComment, type SourceMapNode, type SourceMapOptions, type SourceMapTypes, type SpreadElement, type Statement, type StringLiteral, type TableExpression, type TableField, type TableTypeNode, type TableTypeProperty, type TemplateLiteralType, type TemplateLiteralTypeNode, type Token, type TokenizeOptions, type TupleType, type TupleTypeNode, type Type, type TypeAliasStatement, type TypeAnalysis, type TypeAssertionExpression, type TypeDiagnostic, type TypeLibraries, type TypeLiteralBoolean, type TypeLiteralNumber, type TypeLiteralString, type TypeNode, type TypePackNode, type TypeParamType, type TypePredicate, type TypePredicateNode, type TypeReference, type TypedIdentifier, type TypeofTypeNode, UNUSED_EXPECT_ERROR, type UnaryExpression, UnaryOperators, type UnionType, type UnionTypeNode, type UnknownType, type VarargExpression, type VariableDeclaration, type VariadicTypeNode, type WhileStatement, analyzeScopes, analyzeTypes, anyType, applyDirectives, arrayOf, booleanType, bufferType, containsTypeParam, luautparser as default, difference, directivesOf, equalTypes, falsyType, findConfig, fn, formatType, getBinding, intersection, isAssignable, isClassType, isGlobal, isPossiblyFalsy, isPossiblyTruthy, isUnassignedGlobal, literal, loadConfig, luautparser, matchInfer, moduleCandidates, moduleExports, narrowExclude, narrowFalsy, narrowTo, narrowTruthy, neverType, nilType, nodeHost, numberType, objectType, optional, overlaps, parse, parseExpressionFromSource, parseTokens, parseWithRecovery, primitive, readDirectives, resolveModulePath, resolveTypeLibraries, setAliasExpander, sourceMapTypes, stringType, stripJsonComments, substitute, templateMatches, threadType, tokenize, tuple, typeParam, unify, union, unknownType, widen };
package/dist/index.js CHANGED
@@ -4726,8 +4726,8 @@ var TypeAnalyzer = class {
4726
4726
  /** Recursion guard for `preVisitBody`. */
4727
4727
  preVisitDepth = 0;
4728
4728
  run() {
4729
- this.registerAliasDefs(preludeProgram().body);
4730
- for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
4729
+ this.registerAliasDefs(preludeProgram().body, true);
4730
+ for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
4731
4731
  this.registerAliasDefs(this.program.body);
4732
4732
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
4733
4733
  this.registerImportedTypes();
@@ -4805,10 +4805,28 @@ var TypeAnalyzer = class {
4805
4805
  }
4806
4806
  }
4807
4807
  }
4808
- registerAliasDefs(block) {
4808
+ /** `layering` is on for the prelude and for definitions files: a second
4809
+ * library that declares an alias already declared *adds* to it, the way a
4810
+ * second `declare` of a table's name does, so `@luaut/roblox` can give
4811
+ * `StringMethods` Luau's `split` without restating Lua's. The file being
4812
+ * analysed is not a layer: its own alias replaces what the libraries
4813
+ * gave, which is how a project opts out of a set. */
4814
+ registerAliasDefs(block, layering = false) {
4809
4815
  for (const stmt of block.statements) {
4810
4816
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
4811
- if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4817
+ if (alias) {
4818
+ const previous = layering ? this.aliasDefs.get(alias.name.name) : void 0;
4819
+ const node = previous && !previous.class ? {
4820
+ type: "IntersectionTypeNode",
4821
+ types: [previous.node, alias.definition],
4822
+ line: alias.definition.line,
4823
+ column: alias.definition.column
4824
+ } : alias.definition;
4825
+ this.aliasDefs.set(alias.name.name, {
4826
+ params: previous && !previous.class && previous.params.length ? previous.params : alias.generics,
4827
+ node
4828
+ });
4829
+ }
4812
4830
  if (stmt.type === "DeclareClassStatement") {
4813
4831
  this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4814
4832
  }
@@ -6073,13 +6091,15 @@ var TypeAnalyzer = class {
6073
6091
  const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
6074
6092
  if (!objects.length) return;
6075
6093
  for (const field of e.fields) {
6076
- if (field.type !== "TableFieldNamed") continue;
6077
- const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
6094
+ if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
6095
+ const key = field.type === "TableFieldShorthand" ? field.name.name : field.key.type === "Identifier" ? field.key.name : field.key.value;
6078
6096
  const types = objects.flatMap((o) => {
6079
6097
  const property = o.properties.get(key);
6080
6098
  return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
6081
6099
  });
6082
- if (types.length) this.applyContext(field.value, union(types));
6100
+ if (types.length) {
6101
+ this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
6102
+ }
6083
6103
  }
6084
6104
  }
6085
6105
  /** The members of an expected type worth matching a literal against:
@@ -6878,6 +6898,25 @@ var TypeAnalyzer = class {
6878
6898
  this.resolvingAliases.delete(t.name);
6879
6899
  }
6880
6900
  }
6901
+ /** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
6902
+ * They are written in the prelude as `ArrayMethods<T>` and
6903
+ * `StringMethods`, so a file (or a type library) that declares one of
6904
+ * those names again replaces the whole set, and nothing here is a special
6905
+ * case in the analyzer. The build lowers each call to a plain function. */
6906
+ builtInMethod(t, name) {
6907
+ const element = t.kind === "array" ? t.element : t.kind === "tuple" ? union(t.elements) : void 0;
6908
+ const methodTable = element !== void 0 ? "ArrayMethods" : t.kind === "primitive" && t.name === "string" || t.kind === "literal" && t.base === "string" ? "StringMethods" : void 0;
6909
+ const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
6910
+ if (!def || def.class) return void 0;
6911
+ const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
6912
+ const parts = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
6913
+ for (let i = parts.length - 1; i >= 0; i--) {
6914
+ const part = parts[i];
6915
+ const property = part.kind === "object" ? part.properties.get(name) : void 0;
6916
+ if (property) return property.type;
6917
+ }
6918
+ return void 0;
6919
+ }
6881
6920
  propertyType(raw, name) {
6882
6921
  const t = this.deferredAccess(this.expand(raw));
6883
6922
  if (t.kind === "object") {
@@ -6885,6 +6924,8 @@ var TypeAnalyzer = class {
6885
6924
  if (p) return p.optional ? optional(p.type) : p.type;
6886
6925
  if (t.indexer) return t.indexer.value;
6887
6926
  }
6927
+ const built = this.builtInMethod(t, name);
6928
+ if (built) return built;
6888
6929
  if (t.kind === "union") return union(t.types.map((m) => this.propertyType(m, name)));
6889
6930
  if (t.kind === "intersection") {
6890
6931
  const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
@@ -7238,7 +7279,21 @@ var TypeAnalyzer = class {
7238
7279
  }
7239
7280
  }
7240
7281
  if (asConst && !hadSpread) return tuple(elems);
7241
- return arrayOf(elems.length ? union(elems.map((t) => asConst ? t : widen(t))) : unknownType);
7282
+ return arrayOf(elems.length ? union(elems.map((t, i) => {
7283
+ const element = expr.elements[i];
7284
+ return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
7285
+ })) : unknownType);
7286
+ }
7287
+ /** A literal written inside a fresh table or array widens — `{ n = 1 }` is
7288
+ * `{ n: number }` — unless the surroundings said a literal belongs there.
7289
+ * `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
7290
+ * string literals, exactly as TypeScript's contextual typing does, and
7291
+ * goes on widening to `string` when the parameter only says `string`.
7292
+ * The context was recorded by `applyContext` before the value was
7293
+ * inferred, so this is a lookup rather than a second pass. */
7294
+ widenUnlessAsked(value, at) {
7295
+ const wanted = this.expectedTypeOf.get(at);
7296
+ return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
7242
7297
  }
7243
7298
  inferObject(expr, env, asConst) {
7244
7299
  const entries = [];
@@ -7246,16 +7301,24 @@ var TypeAnalyzer = class {
7246
7301
  for (const field of expr.fields) {
7247
7302
  if (field.type === "TableFieldNamed") {
7248
7303
  const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
7249
- const v = asConst ? this.inferAsConst(field.value, env) : widen(this.infer(field.value, env));
7304
+ const v = asConst ? this.inferAsConst(field.value, env) : this.widenUnlessAsked(this.infer(field.value, env), field.value);
7250
7305
  entries.push([key, { type: v, optional: false, readonly: asConst }]);
7251
7306
  } else if (field.type === "TableFieldShorthand") {
7252
7307
  const v = this.infer(field.name, env);
7253
- entries.push([field.name.name, { type: asConst ? v : widen(v), optional: false, readonly: asConst }]);
7308
+ entries.push([field.name.name, {
7309
+ type: asConst ? v : this.widenUnlessAsked(v, field.name),
7310
+ optional: false,
7311
+ readonly: asConst
7312
+ }]);
7254
7313
  } else if (field.type === "TableFieldComputed") {
7255
7314
  const k = this.infer(field.key, env);
7256
7315
  const v = this.infer(field.value, env);
7257
7316
  if (k.kind === "literal" && typeof k.value === "string") {
7258
- entries.push([k.value, { type: asConst ? v : widen(v), optional: false, readonly: asConst }]);
7317
+ entries.push([k.value, {
7318
+ type: asConst ? v : this.widenUnlessAsked(v, field.value),
7319
+ optional: false,
7320
+ readonly: asConst
7321
+ }]);
7259
7322
  } else {
7260
7323
  indexer = mergeIndexer(indexer, { key: widen(k), value: asConst ? v : widen(v) });
7261
7324
  }
@@ -8080,6 +8143,7 @@ function offsetPosition(source, offset) {
8080
8143
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
8081
8144
  function resolveTypeLibraries(config, host = nodeHost) {
8082
8145
  const files = [];
8146
+ const lowerings = [];
8083
8147
  const problems = [];
8084
8148
  const loaded = /* @__PURE__ */ new Set();
8085
8149
  const addFile = (file) => {
@@ -8097,6 +8161,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
8097
8161
  if (found) addPackage(found.directory, found.file, visiting);
8098
8162
  }
8099
8163
  addFile(entryFile);
8164
+ const lowering = loweringModule(directory, host, problems, config);
8165
+ if (lowering) lowerings.push(lowering);
8100
8166
  };
8101
8167
  for (const entry of config.types) {
8102
8168
  const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
@@ -8123,7 +8189,19 @@ function resolveTypeLibraries(config, host = nodeHost) {
8123
8189
  });
8124
8190
  }
8125
8191
  }
8126
- return { files, problems };
8192
+ return { files, lowerings, problems };
8193
+ }
8194
+ function loweringModule(directory, host, problems, config) {
8195
+ const manifest = readJson(join2(directory, "package.json"), host);
8196
+ const declared = manifest?.luaut?.lowering;
8197
+ if (typeof declared !== "string") return void 0;
8198
+ const from = typeof manifest?.name === "string" ? manifest.name : directory;
8199
+ const file = resolve2(directory, declared);
8200
+ if (host.readFile(file) === void 0) {
8201
+ problems.push({ file: config.path, message: `'${from}' names a lowering module '${declared}', which is not there` });
8202
+ return void 0;
8203
+ }
8204
+ return { file, from };
8127
8205
  }
8128
8206
  var ENTRY_FILE = "index.d.luaut";
8129
8207
  function packageEntry(directory, host) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luaut-parser",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "luaut parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",