Skip to content

Commit

Permalink
Promote state-accessor functions from experimental to supported (#5699
Browse files Browse the repository at this point in the history
)

These reusable state accessors were added in
3eed298 under the `experimental`
namespace. They are now considered ready to be promoted out to the
`@typespec/compiler` package exports (and to have their `unsafe_`
prefixes removed).

In this commit, we move `state-accessor` into `utils` and add it to the
module index so that its functions can be imported from
`@typespec/compiler/utils`.

For usages inside `@typespec/compiler`, we are replacing the wrapper
functions in `lib/utils` with direct imports of the newly-exposed
accessor functions. Because the wrapper functions previously created a
symbol with the `TypeSpec.` prefix when given a string, we preserve this
behavior by introducing the simple `createStateSymbol` function in files
that previously used the wrapper functions.

`createStateSymbol` could be trivially refactored into a shared utility
function rather than duplicated, but here we are simply following the
pattern already established where there are already several copies of
this function. Indeed, while one of these was exported in
050139d, that export was never used.
  • Loading branch information
steverice authored Jan 24, 2025
1 parent ccbc349 commit c788082
Show file tree
Hide file tree
Showing 18 changed files with 108 additions and 81 deletions.
12 changes: 12 additions & 0 deletions .chronus/changes/promote-state-accessor-2025-0-23-9-50-42.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
changeKind: deprecation
packages:
- "@typespec/compiler"
- "@typespec/events"
- "@typespec/json-schema"
- "@typespec/openapi"
- "@typespec/sse"
- "@typespec/streams"
---

Deprecate `unsafe_useStateMap` and `unsafe_useStateSet`, export `useStateMap` and `useStateSet`
16 changes: 10 additions & 6 deletions packages/compiler/src/core/visibility/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from "./lifecycle.js";

import type { VisibilityFilter as GeneratedVisibilityFilter } from "../../../generated-defs/TypeSpec.js";
import { useStateMap, useStateSet } from "../../lib/utils.js";
import { useStateMap, useStateSet } from "../../utils/index.js";

export { GeneratedVisibilityFilter };

Expand All @@ -38,13 +38,17 @@ export { GeneratedVisibilityFilter };
*/
type VisibilityModifiers = Map<Enum, Set<EnumMember>>;

function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

/**
* The global visibility store.
*
* This store is used to track the visibility modifiers
*/
const [getVisibilityStore, setVisibilityStore] = useStateMap<ModelProperty, VisibilityModifiers>(
"visibilityStore",
createStateSymbol("visibilityStore"),
);

/**
Expand Down Expand Up @@ -99,13 +103,13 @@ function getOrInitializeActiveModifierSetForClass(
const VISIBILITY_PROGRAM_SEALS = new WeakSet<Program>();

const [isVisibilitySealedForProperty, sealVisibilityForProperty] = useStateSet<ModelProperty>(
"propertyVisibilitySealed",
createStateSymbol("propertyVisibilitySealed"),
);

const [getSealedVisibilityClasses, setSealedVisibilityClasses] = useStateMap<
ModelProperty,
Set<Enum>
>("sealedVisibilityClasses");
>(createStateSymbol("sealedVisibilityClasses"));

/**
* Seals visibility modifiers for a property in a given visibility class.
Expand Down Expand Up @@ -133,7 +137,7 @@ function sealVisibilityModifiersForClass(
* Stores the default modifier set for a given visibility class.
*/
const [getDefaultModifiers, setDefaultModifiers] = useStateMap<Enum, Set<EnumMember>>(
"defaultVisibilityModifiers",
createStateSymbol("defaultVisibilityModifiers"),
);

/**
Expand Down Expand Up @@ -205,7 +209,7 @@ function groupModifiersByVisibilityClass(modifiers: EnumMember[]): Map<Enum, Set
// #region Legacy Visibility API

const [getLegacyVisibility, setLegacyVisibilityModifiers, getLegacyVisibilityStateMap] =
useStateMap<ModelProperty, string[]>("legacyVisibility");
useStateMap<ModelProperty, string[]>(createStateSymbol("legacyVisibility"));

export { getLegacyVisibility };

Expand Down
12 changes: 9 additions & 3 deletions packages/compiler/src/experimental/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
export {
MutableType as unsafe_MutableType,
mutateSubgraph as unsafe_mutateSubgraph,
mutateSubgraphWithNamespace as unsafe_mutateSubgraphWithNamespace,
Mutator as unsafe_Mutator,
MutatorFilterFn as unsafe_MutatorFilterFn,
MutatorFlow as unsafe_MutatorFlow,
MutatorFn as unsafe_MutatorFn,
MutatorRecord as unsafe_MutatorRecord,
MutatorReplaceFn as unsafe_MutatorReplaceFn,
MutatorWithNamespace as unsafe_MutatorWithNamespace,
mutateSubgraph as unsafe_mutateSubgraph,
mutateSubgraphWithNamespace as unsafe_mutateSubgraphWithNamespace,
} from "./mutators.js";
export { Realm as unsafe_Realm } from "./realm.js";
export { unsafe_useStateMap, unsafe_useStateSet } from "./state-accessor.js";
export { $ as unsafe_$ } from "./typekit/index.js";

import { useStateMap, useStateSet } from "../utils/state-accessor.js";

/** @deprecated use `useStateMap` from `@typespec/compiler/utils` instead */
export const unsafe_useStateMap = useStateMap;
/** @deprecated use `useStateSet` from `@typespec/compiler/utils` instead */
export const unsafe_useStateSet = useStateSet;
40 changes: 25 additions & 15 deletions packages/compiler/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ import {
UnionVariant,
Value,
} from "../core/types.js";
import { useStateMap, useStateSet } from "../utils/index.js";
import { setKey } from "./key.js";
import { useStateMap, useStateSet } from "./utils.js";

export { $encodedName, resolveEncodedName } from "./encoded-names.js";
export { serializeValueAsJson } from "./examples.js";
Expand All @@ -121,11 +121,11 @@ function replaceTemplatedStringFromProperties(formatString: string, sourceObject
});
}

export function createStateSymbol(name: string) {
function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

const [getSummary, setSummary] = useStateMap<Type, string>("summary");
const [getSummary, setSummary] = useStateMap<Type, string>(createStateSymbol("summary"));
/**
* @summary attaches a documentation string. It is typically used to give a short, single-line
* description, and can be used in combination with or instead of @doc.
Expand Down Expand Up @@ -326,7 +326,7 @@ function validateTargetingAString(

// -- @error decorator ----------------------

const [getErrorState, setErrorState] = useStateSet<Model>("error");
const [getErrorState, setErrorState] = useStateSet<Model>(createStateSymbol("error"));
/**
* `@error` decorator marks a model as an error type.
* Any derived models (using extends) will also be seen as error types.
Expand Down Expand Up @@ -355,7 +355,7 @@ export function isErrorModel(program: Program, target: Type): boolean {

// -- @format decorator ---------------------

const [getFormat, setFormat] = useStateMap<Type, string>("format");
const [getFormat, setFormat] = useStateMap<Type, string>(createStateSymbol("format"));

/**
* `@format` - specify the data format hint for a string type
Expand Down Expand Up @@ -395,7 +395,9 @@ export const $format: FormatDecorator = (
export { getFormat };

// -- @pattern decorator ---------------------
const [getPatternData, setPatternData] = useStateMap<Type, PatternData>("patternValues");
const [getPatternData, setPatternData] = useStateMap<Type, PatternData>(
createStateSymbol("patternValues"),
);

export interface PatternData {
readonly pattern: string;
Expand Down Expand Up @@ -656,7 +658,7 @@ export const $maxValueExclusive: MaxValueExclusiveDecorator = (
};
// -- @secret decorator ---------------------

const [isSecret, markSecret] = useStateSet("secretTypes");
const [isSecret, markSecret] = useStateSet(createStateSymbol("secretTypes"));

/**
* Mark a string as a secret value that should be treated carefully to avoid exposure
Expand Down Expand Up @@ -690,7 +692,9 @@ export interface EncodeData {
type: Scalar;
}

const [getEncode, setEncodeData] = useStateMap<Scalar | ModelProperty, EncodeData>("encode");
const [getEncode, setEncodeData] = useStateMap<Scalar | ModelProperty, EncodeData>(
createStateSymbol("encode"),
);
export const $encode: EncodeDecorator = (
context: DecoratorContext,
target: Scalar | ModelProperty,
Expand Down Expand Up @@ -893,7 +897,7 @@ export const $withoutDefaultValues: WithoutDefaultValuesDecorator = (

// -- @tag decorator ---------------------

const [getTagsState, setTags] = useStateMap<Type, string[]>("tagProperties");
const [getTagsState, setTags] = useStateMap<Type, string[]>(createStateSymbol("tagProperties"));

// Set a tag on an operation, interface, or namespace. There can be multiple tags on an
// operation, interface, or namespace.
Expand Down Expand Up @@ -943,7 +947,9 @@ export function getAllTags(

// -- @friendlyName decorator ---------------------

const [getFriendlyName, setFriendlyName] = useStateMap<Type, string>("friendlyNames");
const [getFriendlyName, setFriendlyName] = useStateMap<Type, string>(
createStateSymbol("friendlyNames"),
);
export const $friendlyName: FriendlyNameDecorator = (
context: DecoratorContext,
target: Type,
Expand Down Expand Up @@ -981,7 +987,7 @@ export const $friendlyName: FriendlyNameDecorator = (

export { getFriendlyName };

const [getKnownValues, setKnownValues] = useStateMap<Type, Enum>("knownValues");
const [getKnownValues, setKnownValues] = useStateMap<Type, Enum>(createStateSymbol("knownValues"));

/**
* `@knownValues` marks a string type with an enum that contains all known values
Expand Down Expand Up @@ -1100,9 +1106,11 @@ export function getDeprecated(program: Program, type: Type): string | undefined
return getDeprecationDetails(program, type)?.message;
}

const [getOverloads, setOverloads] = useStateMap<Operation, Operation[]>("overloadedByKey");
const [getOverloads, setOverloads] = useStateMap<Operation, Operation[]>(
createStateSymbol("overloadedByKey"),
);
const [getOverloadedOperation, setOverloadBase] = useStateMap<Operation, Operation>(
"overloadsOperation",
createStateSymbol("overloadsOperation"),
);

/**
Expand Down Expand Up @@ -1293,7 +1301,7 @@ export interface OpExample extends ExampleOptions {
const [getExamplesState, setExamples] = useStateMap<
Model | Scalar | Enum | Union | ModelProperty | UnionVariant,
Example[]
>("examples");
>(createStateSymbol("examples"));
export const $example: ExampleDecorator = (
context: DecoratorContext,
target: Model | Scalar | Enum | Union | ModelProperty | UnionVariant,
Expand Down Expand Up @@ -1334,7 +1342,9 @@ export function getExamples(
return getExamplesState(program, target) ?? [];
}

const [getOpExamplesState, setOpExamples] = useStateMap<Operation, OpExample[]>("opExamples");
const [getOpExamplesState, setOpExamples] = useStateMap<Operation, OpExample[]>(
createStateSymbol("opExamples"),
);
export const $opExample: OpExampleDecorator = (
context: DecoratorContext,
target: Operation,
Expand Down
9 changes: 6 additions & 3 deletions packages/compiler/src/lib/encoded-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { reportDiagnostic } from "../core/messages.js";
import { parseMimeType } from "../core/mime-type.js";
import type { Program } from "../core/program.js";
import type { DecoratorContext, Enum, Model, Type, Union } from "../core/types.js";
import { DuplicateTracker } from "../utils/index.js";
import { useStateMap } from "./utils.js";
import { DuplicateTracker, useStateMap } from "../utils/index.js";

function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

const [getEncodedNamesMap, setEncodedNamesMap, getEncodedNamesStateMap] = useStateMap<
Type,
Map<string, string>
>("encodedName");
>(createStateSymbol("encodedName"));

export function $encodedName(
context: DecoratorContext,
Expand Down
8 changes: 6 additions & 2 deletions packages/compiler/src/lib/key.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Program } from "../core/index.js";
import { ModelProperty, Type } from "../core/types.js";
import { useStateMap } from "./utils.js";
import { useStateMap } from "../utils/index.js";

const [getKey, setKey] = useStateMap<Type, string>("key");
function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

const [getKey, setKey] = useStateMap<Type, string>(createStateSymbol("key"));

export function isKey(program: Program, property: ModelProperty) {
return getKey(program, property) !== undefined;
Expand Down
9 changes: 6 additions & 3 deletions packages/compiler/src/lib/paging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ import type {
Operation,
Type,
} from "../core/types.js";
import { DuplicateTracker } from "../utils/duplicate-tracker.js";
import { DuplicateTracker, useStateSet } from "../utils/index.js";
import { isNumericType, isStringType } from "./decorators.js";
import { useStateSet } from "./utils.js";

export const [
/**
Expand Down Expand Up @@ -342,11 +341,15 @@ function validatePagingOperation(program: Program, op: Operation) {
program.reportDiagnostics(diagnostics);
}

function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

function createMarkerDecorator<T extends DecoratorFunction>(
key: string,
validate?: (...args: Parameters<T>) => boolean,
) {
const [isLink, markLink] = useStateSet<Parameters<T>[1]>(key);
const [isLink, markLink] = useStateSet<Parameters<T>[1]>(createStateSymbol(key));
const decorator = (...args: Parameters<T>) => {
if (validate && !validate(...args)) {
return;
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Type, getTypeName, reportDeprecated } from "../core/index.js";
import { reportDiagnostic } from "../core/messages.js";
import type { Program } from "../core/program.js";
import { DecoratorContext, Namespace } from "../core/types.js";
import { useStateMap } from "./utils.js";
import { useStateMap } from "../utils/index.js";

export interface ServiceDetails {
title?: string;
Expand Down
15 changes: 1 addition & 14 deletions packages/compiler/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
import type { Model, ModelProperty, Type } from "../core/types.js";
import { unsafe_useStateMap, unsafe_useStateSet } from "../experimental/state-accessor.js";

function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

export function useStateMap<K extends Type, V>(key: string | symbol) {
return unsafe_useStateMap<K, V>(typeof key === "string" ? createStateSymbol(key) : key);
}

export function useStateSet<K extends Type>(key: string | symbol) {
return unsafe_useStateSet<K>(typeof key === "string" ? createStateSymbol(key) : key);
}
import type { Model, ModelProperty } from "../core/types.js";

/**
* Filters the properties of a model by removing them from the model instance if
Expand Down
9 changes: 7 additions & 2 deletions packages/compiler/src/lib/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,16 @@ import {
normalizeVisibilityToLegacyLifecycleString,
} from "../core/visibility/lifecycle.js";
import { isMutableType, mutateSubgraph, Mutator, MutatorFlow } from "../experimental/mutators.js";
import { useStateMap } from "../utils/index.js";
import { isKey } from "./key.js";
import { filterModelPropertiesInPlace, useStateMap } from "./utils.js";
import { filterModelPropertiesInPlace } from "./utils.js";

// #region Legacy Visibility Utilities

function createStateSymbol(name: string) {
return Symbol.for(`TypeSpec.${name}`);
}

/**
* Takes a list of visibilities that possibly include both legacy visibility
* strings and visibility class members, and returns two lists containing only
Expand Down Expand Up @@ -136,7 +141,7 @@ interface OperationVisibilityConfig {
const [getOperationVisibilityConfigRaw, setOperationVisibilityConfigRaw] = useStateMap<
Operation,
OperationVisibilityConfig
>("operationVisibilityConfig");
>(createStateSymbol("operationVisibilityConfig"));

function getOperationVisibilityConfig(
program: Program,
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
// ---------------------------------------
export { DuplicateTracker } from "./duplicate-tracker.js";
export { Queue, TwoLevelMap, createRekeyableMap, deepClone, deepEquals } from "./misc.js";
export { useStateMap, useStateSet } from "./state-accessor.js";
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ type StateMapGetter<K extends Type, V> = (program: Program, type: K) => V | unde
type StateMapSetter<K extends Type, V> = (program: Program, type: K, value: V) => void;
type StateMapMapGetter<K extends Type, V> = (program: Program) => Map<K, V>;

/** @experimental */
export function unsafe_useStateMap<K extends Type, V>(
export function useStateMap<K extends Type, V>(
key: symbol,
): [StateMapGetter<K, V>, StateMapSetter<K, V>, StateMapMapGetter<K, V>] {
const getter = (program: Program, target: K) => program.stateMap(key).get(target);
Expand All @@ -19,10 +18,7 @@ export function unsafe_useStateMap<K extends Type, V>(
type StateSetGetter<K extends Type> = (program: Program, type: K) => boolean;
type StateSetSetter<K extends Type> = (program: Program, type: K) => void;

/** @experimental */
export function unsafe_useStateSet<K extends Type>(
key: symbol,
): [StateSetGetter<K>, StateSetSetter<K>] {
export function useStateSet<K extends Type>(key: symbol): [StateSetGetter<K>, StateSetSetter<K>] {
const getter = (program: Program, target: K) => program.stateSet(key).has(target);
const setter = (program: Program, target: K) => program.stateSet(key).add(target);

Expand Down
Loading

0 comments on commit c788082

Please sign in to comment.