⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .chronus/changes/fionabronwen-type-utils-2026-0-12-16-12-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
changeKind: deprecation
packages:
- "@typespec/compiler"
- "@typespec/http"
- "@typespec/openapi3"
- "@typespec/tspd"
- "@typespec/http-server-js"
- "@typespec/http-server-csharp"
---

Deprecate `program` parameter in `isArrayModelType` and `isRecordModelType` functions. Use the new single-argument overload instead: `isArrayModelType(type)` and `isRecordModelType(type)`.
9 changes: 3 additions & 6 deletions packages/compiler/src/core/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4730,7 +4730,7 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
);
return [[], undefined];
}
if (isArrayModelType(program, targetType)) {
if (isArrayModelType(targetType)) {
reportCheckerDiagnostic(
createDiagnostic({ code: "spread-model", target: targetNode }),
mapper,
Expand Down Expand Up @@ -5096,17 +5096,14 @@ export function createChecker(program: Program, resolver: NameResolver): Checker
let valueType: Type | undefined;
let type: Type | undefined;
if (constraint.valueType) {
if (
constraint.valueType.kind === "Model" &&
isArrayModelType(program, constraint.valueType)
) {
if (constraint.valueType.kind === "Model" && isArrayModelType(constraint.valueType)) {
valueType = constraint.valueType.indexer.value;
} else {
return undefined;
}
}
if (constraint.type) {
if (constraint.type.kind === "Model" && isArrayModelType(program, constraint.type)) {
if (constraint.type.kind === "Model" && isArrayModelType(constraint.type)) {
type = constraint.type.indexer.value;
} else {
return undefined;
Expand Down
14 changes: 3 additions & 11 deletions packages/compiler/src/core/type-relation-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,8 @@ export function createTypeRelationChecker(program: Program, checker: Checker): T
}),
],
];
} else if (
target.kind === "Model" &&
isArrayModelType(program, target) &&
source.kind === "Model"
) {
if (isArrayModelType(program, source)) {
} else if (target.kind === "Model" && isArrayModelType(target) && source.kind === "Model") {
if (isArrayModelType(source)) {
return hasIndexAndIsAssignableTo(
source,
target as Model & { indexer: ModelIndexer },
Expand All @@ -339,11 +335,7 @@ export function createTypeRelationChecker(program: Program, checker: Checker): T
}
} else if (target.kind === "Model" && source.kind === "Model") {
return areModelsRelated(source, target, diagnosticTarget, relationCache);
} else if (
target.kind === "Model" &&
isArrayModelType(program, target) &&
source.kind === "Tuple"
) {
} else if (target.kind === "Model" && isArrayModelType(target) && source.kind === "Tuple") {
return isTupleAssignableToArray(source, target, diagnosticTarget, relationCache);
} else if (target.kind === "Tuple" && source.kind === "Tuple") {
return isTupleAssignableToTuple(source, target, diagnosticTarget, relationCache);
Expand Down
26 changes: 23 additions & 3 deletions packages/compiler/src/core/type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Node,
NullType,
Operation,
RecordModelType,
Sym,
SymbolFlags,
SyntaxKind,
Expand Down Expand Up @@ -51,17 +52,36 @@ export function isValue(entity: Entity): entity is Value {
}

/**
* Check if a model is an array type.
* @param program Program (unused)
* @param type Model type
* @deprecated Use `isArrayModelType(type)` instead. The `program` parameter is unused.
*/
export function isArrayModelType(program: Program, type: Model): type is ArrayModelType;
/**
* Check if a model is an array type.
* @param type Model type
*/
export function isArrayModelType(program: Program, type: Model): type is ArrayModelType {
export function isArrayModelType(type: Model): type is ArrayModelType;
export function isArrayModelType(programOrType: Program | Model, maybeType?: Model): boolean {
const type = maybeType ?? (programOrType as Model);
return Boolean(type.indexer && type.indexer.key.name === "integer");
}

/**
* Check if a model is an array type.
* Check if a model is a record type.
* @param program Program (unused)
* @param type Model type
* @deprecated Use `isRecordModelType(type)` instead. The `program` parameter is unused.
*/
export function isRecordModelType(program: Program, type: Model): type is RecordModelType;
/**
* Check if a model is a record type.
* @param type Model type
*/
export function isRecordModelType(program: Program, type: Model): type is ArrayModelType {
export function isRecordModelType(type: Model): type is RecordModelType;
export function isRecordModelType(programOrType: Program | Model, maybeType?: Model): boolean {
const type = maybeType ?? (programOrType as Model);
return Boolean(type.indexer && type.indexer.key.name === "string");
}

Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export {
type NavigationOptions,
} from "./core/semantic-walker.js";
export { createSourceFile, getSourceFileKindFromExt } from "./core/source-file.js";
/* eslint-disable @typescript-eslint/no-deprecated -- exporting deprecated overloads for backward compatibility */
export {
isArrayModelType,
isDeclaredInNamespace,
Expand All @@ -301,6 +302,7 @@ export {
isValue,
isVoidType,
} from "./core/type-utils.js";
/* eslint-enable @typescript-eslint/no-deprecated */
export { ListenerFlow, NoTarget } from "./core/types.js";
export type {
ArrayModelType,
Expand Down
5 changes: 1 addition & 4 deletions packages/compiler/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,7 @@ function validateTargetingAnArray(
decoratorName: string,
) {
const targetType = getTypeForArrayValidation(target);
const valid = isTypeIn(
targetType,
(x) => x.kind === "Model" && isArrayModelType(context.program, x),
);
const valid = isTypeIn(targetType, (x) => x.kind === "Model" && isArrayModelType(x));
if (!valid) {
reportDiagnostic(context.program, {
code: "decorator-wrong-target",
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/lib/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function serializeValueAsJson(
serializeValueAsJson(
program,
v,
type.kind === "Model" && isArrayModelType(program, type)
type.kind === "Model" && isArrayModelType(type)
? type.indexer.value
: program.checker.anyType,
),
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/lib/paging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const [
/** {@inheritdoc PageItemsDecorator} */
pageItemsDecorator,
] = createMarkerDecorator<PageItemsDecorator>("pageItems", (context, target) => {
if (target.type.kind !== "Model" || !isArrayModelType(context.program, target.type)) {
if (target.type.kind !== "Model" || !isArrayModelType(target.type)) {
reportDiagnostic(context.program, {
code: "decorator-wrong-target",
messageId: "withExpected",
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/typekit/kits/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defineKit<TypekitExtension>({
return (
type.entityKind === "Type" &&
type.kind === "Model" &&
isArrayModelType(this.program, type) &&
isArrayModelType(type) &&
type.properties.size === 0
);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/typekit/kits/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defineKit<TypekitExtension>({
return (
type.entityKind === "Type" &&
type.kind === "Model" &&
isRecordModelType(this.program, type) &&
isRecordModelType(type) &&
type.properties.size === 0
);
},
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler/test/checker/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ describe("compiler: models", () => {
`,
);
const { A } = (await testHost.compile("main.tsp")) as { A: Model };
ok(isArrayModelType(testHost.program, A));
ok(isArrayModelType(A));
});

it("model is accept array expression of complex type", async () => {
Expand All @@ -859,7 +859,7 @@ describe("compiler: models", () => {
`,
);
const { A } = (await testHost.compile("main.tsp")) as { A: Model };
ok(isArrayModelType(testHost.program, A));
ok(isArrayModelType(A));
strictEqual(A.indexer.value.kind, "Union");
});

Expand Down Expand Up @@ -1124,7 +1124,7 @@ describe("compiler: models", () => {
const { Test } = (await testHost.compile("main.tsp")) as {
Test: Model;
};
ok(isRecordModelType(testHost.program, Test));
ok(isRecordModelType(Test));
strictEqual(Test.indexer?.key.name, "string");
strictEqual(Test.indexer?.value.kind, "Scalar");
strictEqual(Test.indexer?.value.name, "int32");
Expand All @@ -1143,7 +1143,7 @@ describe("compiler: models", () => {
const { Test } = (await testHost.compile("main.tsp")) as {
Test: Model;
};
ok(isRecordModelType(testHost.program, Test));
ok(isRecordModelType(Test));
const nameProp = Test.properties.get("name");
strictEqual(nameProp?.type.kind, "Scalar");
strictEqual(nameProp?.type.name, "string");
Expand All @@ -1165,7 +1165,7 @@ describe("compiler: models", () => {
const { Test } = (await testHost.compile("main.tsp")) as {
Test: Model;
};
ok(isRecordModelType(testHost.program, Test));
ok(isRecordModelType(Test));
strictEqual(Test.indexer?.key.name, "string");
const indexerValue = Test.indexer?.value;
strictEqual(indexerValue.kind, "Union");
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server-csharp/src/lib/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export function getArrayConstraintAttribute(
const maxItems = getMaxItems(program, type);
if (minItems === undefined && maxItems === undefined) return undefined;
if (type.kind !== "ModelProperty" || type.type.kind !== "Model") return undefined;
if (!isArrayModelType(program, type.type)) return undefined;
if (!isArrayModelType(type.type)) return undefined;
const arrayType = type.type;
const elementType = arrayType.indexer.value;
if (elementType.kind !== "Scalar") return undefined;
Expand Down
6 changes: 1 addition & 5 deletions packages/http-server-csharp/src/lib/type-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,7 @@ export function getEnumType(en: Enum): EnumValueType | undefined {
* @returns true if the type is an array or a model property with array type, otherwise false
*/
export function isArrayType(program: Program, type: ModelProperty | Scalar): boolean {
return (
type.kind === "ModelProperty" &&
type.type.kind === "Model" &&
isArrayModelType(program, type.type)
);
return type.kind === "ModelProperty" && type.type.kind === "Model" && isArrayModelType(type.type);
}

/** Inner representation of s string constraint */
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server-csharp/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1586,7 +1586,7 @@ export class CSharpOperationHelpers {
nullableType: false,
hasUniqueItems: hasUniqueItems,
};
} else if (isArrayModelType(program, tsType)) {
} else if (isArrayModelType(tsType)) {
const typeReference = code`${this.emitter.emitTypeReference(tsType.indexer.value)}`;
modelResult = isByteType(tsType.indexer.value)
? {
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server-js/src/common/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function emitTypeReference(
return getJsScalar(ctx, module, type, position).type;
case "Model": {
// First handle arrays.
if (isArrayModelType(ctx.program, type)) {
if (isArrayModelType(type)) {
const argumentType = type.indexer.value;

const argTypeReference = emitTypeReference(ctx, argumentType, position, module, {
Expand Down
10 changes: 5 additions & 5 deletions packages/http-server-js/src/common/serialization/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function requiresJsonSerialization(

switch (type.kind) {
case "Model": {
if (isArrayModelType(ctx.program, type)) {
if (isArrayModelType(type)) {
const argumentType = type.indexer.value;
requiresSerialization = requiresJsonSerialization(ctx, module, argumentType);
break;
Expand Down Expand Up @@ -237,15 +237,15 @@ export function transposeExpressionToJson(
): string {
switch (type.kind) {
case "Model": {
if (isArrayModelType(ctx.program, type)) {
if (isArrayModelType(type)) {
const argumentType = type.indexer.value;

if (requiresJsonSerialization(ctx, module, argumentType)) {
return `(${expr})?.map((item) => ${transposeExpressionToJson(ctx, argumentType, "item", module)})`;
} else {
return expr;
}
} else if (isRecordModelType(ctx.program, type)) {
} else if (isRecordModelType(type)) {
const argumentType = type.indexer.value;

if (requiresJsonSerialization(ctx, module, argumentType)) {
Expand Down Expand Up @@ -480,15 +480,15 @@ export function transposeExpressionFromJson(
): string {
switch (type.kind) {
case "Model": {
if (isArrayModelType(ctx.program, type)) {
if (isArrayModelType(type)) {
const argumentType = type.indexer.value;

if (requiresJsonSerialization(ctx, module, argumentType)) {
return `(${expr})?.map((item: any) => ${transposeExpressionFromJson(ctx, argumentType, "item", module)})`;
} else {
return expr;
}
} else if (isRecordModelType(ctx.program, type)) {
} else if (isRecordModelType(type)) {
const argumentType = type.indexer.value;

if (requiresJsonSerialization(ctx, module, argumentType)) {
Expand Down
4 changes: 1 addition & 3 deletions packages/http-server-js/src/ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ export type DeclarationType = Model | Enum | Union | Interface | Scalar;
*/
export function isImportableType(ctx: JsContext, t: Type): t is DeclarationType {
return (
(t.kind === "Model" &&
!isArrayModelType(ctx.program, t) &&
!isRecordModelType(ctx.program, t)) ||
(t.kind === "Model" && !isArrayModelType(t) && !isRecordModelType(t)) ||
t.kind === "Enum" ||
t.kind === "Interface"
);
Expand Down
8 changes: 4 additions & 4 deletions packages/http-server-js/src/http/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function* emitRawServerOperation(
let value: string;

if (requiresJsonSerialization(ctx, module, body.type)) {
if (body.type.kind === "Model" && isArrayModelType(ctx.program, body.type)) {
if (body.type.kind === "Model" && isArrayModelType(body.type)) {
yield ` const __arrayBody = JSON.parse(body);`;
yield ` if (!Array.isArray(__arrayBody)) {`;
yield ` ${names.ctx}.errorHandlers.onInvalidRequest(`;
Expand All @@ -261,7 +261,7 @@ function* emitRawServerOperation(
yield ` return reject();`;
yield ` }`;
value = transposeExpressionFromJson(ctx, body.type, `__arrayBody`, module);
} else if (body.type.kind === "Model" && isRecordModelType(ctx.program, body.type)) {
} else if (body.type.kind === "Model" && isRecordModelType(body.type)) {
yield ` const __recordBody = JSON.parse(body);`;
yield ` if (typeof __recordBody !== "object" || __recordBody === null) {`;
yield ` ${names.ctx}.errorHandlers.onInvalidRequest(`;
Expand Down Expand Up @@ -641,7 +641,7 @@ function* emitResultProcessingForType(
} else {
yield `${names.ctx}.response.end(globalThis.JSON.stringify(${names.result}.${bodyCase.camelCase}));`;
}
} else if (isArrayModelType(ctx.program, target)) {
} else if (isArrayModelType(target)) {
const itemType = target.indexer.value;

const serializationRequired = isSerializationRequired(
Expand All @@ -659,7 +659,7 @@ function* emitResultProcessingForType(
} else {
yield `${names.ctx}.response.end(globalThis.JSON.stringify(${names.result}));`;
}
} else if (isRecordModelType(ctx.program, target)) {
} else if (isRecordModelType(target)) {
const itemType = target.indexer.value;

const serializationRequired = isSerializationRequired(
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server-js/src/util/differentiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ export function differentiateModelTypes(
let arrayVariant: Model | undefined = undefined;

for (const model of models) {
if (isArrayModelType(ctx.program, model) && model.properties.size === 0 && !arrayVariant) {
if (isArrayModelType(model) && model.properties.size === 0 && !arrayVariant) {
arrayVariant = model;
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/experimental/merge-patch/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function getMergePatchProperties(
function getUpdateBehavior(type: Type): "merge" | "replace" {
switch (type.kind) {
case "Model":
if (isArrayModelType(program, type)) return "merge";
if (isArrayModelType(type)) return "merge";
return "replace";
default:
return "replace";
Expand Down
4 changes: 2 additions & 2 deletions packages/http/src/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function resolveBody(
}

// non-model or intrinsic/array model -> response body is response type
if (requestOrResponseType.kind !== "Model" || isArrayModelType(program, requestOrResponseType)) {
if (requestOrResponseType.kind !== "Model" || isArrayModelType(requestOrResponseType)) {
return diagnostics.wrap({
bodyKind: "single",
...diagnostics.pipe(
Expand Down Expand Up @@ -516,7 +516,7 @@ function resolvePartOrParts(
visibility: Visibility,
property?: ModelProperty,
): DiagnosticResult<HttpOperationPartCommon | undefined> {
if (type.kind === "Model" && isArrayModelType(program, type)) {
if (type.kind === "Model" && isArrayModelType(type)) {
const [part, diagnostics] = resolvePart(program, type.indexer.value, visibility, property);
if (part) {
return [{ ...part, multi: true }, diagnostics];
Expand Down
Loading
Loading