Skip to content

Commit e0fb2d1

Browse files
feat: move internal modifier out of experimental (microsoft#10855)
The `internal` access modifier has been stable with no issues, so it no longer needs the experimental warning. This removes the `experimental-feature` diagnostic emitted on every use of `internal` and cleans up all associated suppressions. ## Changes - **`modifiers.ts`** — Remove the block that emitted `experimental-feature` (`messageId: "internal"`) whenever the `internal` modifier was used - **`messages.ts`** — Remove the `internal` messageId from the `experimental-feature` diagnostic definition - **`prototypes.tsp`** — Remove `#suppress "experimental-feature"` from `internal extern dec` (decorator declarations were never independently experimental; the suppress was only needed for `internal`) - **`visibility.tsp`, `private.decorators.tsp`** — Retain existing `#suppress "experimental-feature"` on `internal extern fn` declarations — function declarations (`fn`) remain experimental - **`internal.test.ts`** — Update tests to reflect `internal` no longer emits any diagnostic; previously-allowed usages that only expected `experimental-feature` now expect clean compilation - **`access-modifiers.md`** — Remove the "Suppressing the experimental warning" section ### Before / After ```typespec // Before: emits experimental-feature warning, requires suppression #suppress "experimental-feature" "suppress internal warning" internal model MyInternalModel {} // After: no warning, no suppression needed internal model MyInternalModel {} ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
1 parent a54846f commit e0fb2d1

6 files changed

Lines changed: 29 additions & 92 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/compiler"
5+
---
6+
7+
The `internal` modifier is no longer experimental. Using `internal` will no longer emit an `experimental-feature` warning, and `#suppress "experimental-feature"` directives are no longer needed.

packages/compiler/lib/prototypes.tsp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace TypeSpec.Prototypes;
22

3-
#suppress "experimental-feature" "Compiler internal decorator."
43
internal extern dec getter(target: unknown);
54

65
namespace Types {

packages/compiler/src/core/messages.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ const diagnostics = {
267267
default: paramMessage`${"feature"} is an experimental feature. It may change in the future or be removed. Use with caution and consider providing feedback on this feature.`,
268268
functionDeclarations:
269269
"Function declarations are an experimental feature that may change in the future. Use with caution and consider providing feedback to the TypeSpec team.",
270-
internal: `Internal symbols are experimental and may be changed in a future release. Use with caution. Suppress this message ('#suppress "experimental-feature"') to silence this warning.`,
271270
},
272271
},
273272
"using-invalid-ref": {

packages/compiler/src/core/modifiers.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,6 @@ export function checkModifiers(program: Program, node: Declaration): boolean {
6666

6767
let isValid = true;
6868

69-
// Emit experimental warning for any use of the 'internal' modifier.
70-
if (node.modifierFlags & ModifierFlags.Internal) {
71-
const internalModifiers = filterModifiersByFlags(node.modifiers, ModifierFlags.Internal);
72-
for (const modifier of internalModifiers) {
73-
program.reportDiagnostic(
74-
createDiagnostic({
75-
code: "experimental-feature",
76-
messageId: "internal",
77-
target: modifier,
78-
}),
79-
);
80-
}
81-
}
82-
8369
const invalidModifiers = node.modifierFlags & ~compatibility.allowed;
8470

8571
if (invalidModifiers) {

packages/compiler/test/checker/internal.test.ts

Lines changed: 22 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@ describe("modifier validation", () => {
1515
];
1616

1717
for (const { keyword, code } of declarationKinds) {
18-
it(`allows 'internal' on ${keyword} declaration (with experimental warning)`, async () => {
18+
it(`allows 'internal' on ${keyword} declaration`, async () => {
1919
const diagnostics = await Tester.diagnose(code);
20-
expectDiagnostics(diagnostics, {
21-
code: "experimental-feature",
22-
severity: "warning",
23-
message: `Internal symbols are experimental and may be changed in a future release. Use with caution. Suppress this message ('#suppress "experimental-feature"') to silence this warning.`,
24-
});
20+
expectDiagnosticEmpty(diagnostics);
2521
});
2622
}
2723

@@ -32,18 +28,12 @@ describe("modifier validation", () => {
3228
.import("./test.js")
3329
.diagnose(`internal extern dec myDec(target: unknown);`);
3430

35-
// Only the experimental warning, no error
36-
expectDiagnostics(diagnostics, {
37-
code: "experimental-feature",
38-
});
31+
expectDiagnosticEmpty(diagnostics);
3932
});
4033

4134
it("does not allow 'internal' on namespace", async () => {
4235
const diagnostics = await Tester.diagnose(`internal namespace Foo {}`);
4336
expectDiagnostics(diagnostics, [
44-
{
45-
code: "experimental-feature",
46-
},
4737
{
4838
code: "invalid-modifier",
4939
message: "Modifier 'internal' cannot be used on declarations of type 'namespace'.",
@@ -54,9 +44,6 @@ describe("modifier validation", () => {
5444
it("does not allow 'internal' on blockless namespace", async () => {
5545
const diagnostics = await Tester.diagnose(`internal namespace Foo;`);
5646
expectDiagnostics(diagnostics, [
57-
{
58-
code: "experimental-feature",
59-
},
6047
{
6148
code: "invalid-modifier",
6249
message: "Modifier 'internal' cannot be used on declarations of type 'namespace'.",
@@ -123,10 +110,7 @@ describe("access control", () => {
123110
model Consumer { x: LibModel }
124111
`);
125112

126-
expectDiagnostics(diagnostics, [
127-
{ code: "invalid-ref", message: /internal/ },
128-
{ code: "experimental-feature" },
129-
]);
113+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
130114
});
131115

132116
it("rejects access to internal scalar from another package", async () => {
@@ -137,10 +121,7 @@ describe("access control", () => {
137121
model Consumer { x: LibScalar }
138122
`);
139123

140-
expectDiagnostics(diagnostics, [
141-
{ code: "invalid-ref", message: /internal/ },
142-
{ code: "experimental-feature" },
143-
]);
124+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
144125
});
145126

146127
it("rejects access to internal interface from another package", async () => {
@@ -151,10 +132,7 @@ describe("access control", () => {
151132
interface Consumer extends LibIface {}
152133
`);
153134

154-
expectDiagnostics(diagnostics, [
155-
{ code: "invalid-ref", message: /internal/ },
156-
{ code: "experimental-feature" },
157-
]);
135+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
158136
});
159137

160138
it("rejects access to internal union from another package", async () => {
@@ -165,10 +143,7 @@ describe("access control", () => {
165143
model Consumer { x: LibUnion }
166144
`);
167145

168-
expectDiagnostics(diagnostics, [
169-
{ code: "invalid-ref", message: /internal/ },
170-
{ code: "experimental-feature" },
171-
]);
146+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
172147
});
173148

174149
it("rejects access to internal op from another package", async () => {
@@ -179,10 +154,7 @@ describe("access control", () => {
179154
op consumer is libOp;
180155
`);
181156

182-
expectDiagnostics(diagnostics, [
183-
{ code: "invalid-ref", message: /internal/ },
184-
{ code: "experimental-feature" },
185-
]);
157+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
186158
});
187159

188160
it("rejects access to internal enum from another package", async () => {
@@ -193,10 +165,7 @@ describe("access control", () => {
193165
model Consumer { x: LibEnum }
194166
`);
195167

196-
expectDiagnostics(diagnostics, [
197-
{ code: "invalid-ref", message: /internal/ },
198-
{ code: "experimental-feature" },
199-
]);
168+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
200169
});
201170

202171
it("rejects access to internal alias from another package", async () => {
@@ -210,10 +179,7 @@ describe("access control", () => {
210179
model Consumer { x: LibAlias }
211180
`);
212181

213-
expectDiagnostics(diagnostics, [
214-
{ code: "invalid-ref", message: /internal/ },
215-
{ code: "experimental-feature" },
216-
]);
182+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
217183
});
218184

219185
it("rejects access to internal model in a namespace from another package", async () => {
@@ -227,10 +193,7 @@ describe("access control", () => {
227193
model Consumer { x: MyLib.Secret }
228194
`);
229195

230-
expectDiagnostics(diagnostics, [
231-
{ code: "invalid-ref", message: /internal/ },
232-
{ code: "experimental-feature" },
233-
]);
196+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
234197
});
235198

236199
it("rejects access to internal model via 'using' from another package", async () => {
@@ -245,10 +208,7 @@ describe("access control", () => {
245208
model Consumer { x: Secret }
246209
`);
247210

248-
expectDiagnostics(diagnostics, [
249-
{ code: "invalid-ref", message: /internal/ },
250-
{ code: "experimental-feature" },
251-
]);
211+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
252212
});
253213

254214
it("rejects extending an internal model from another package", async () => {
@@ -259,10 +219,7 @@ describe("access control", () => {
259219
model Consumer extends Base {}
260220
`);
261221

262-
expectDiagnostics(diagnostics, [
263-
{ code: "invalid-ref", message: /internal/ },
264-
{ code: "experimental-feature" },
265-
]);
222+
expectDiagnostics(diagnostics, [{ code: "invalid-ref", message: /internal/ }]);
266223
});
267224
});
268225

@@ -273,8 +230,7 @@ describe("access control", () => {
273230
model Consumer { x: Secret }
274231
`);
275232

276-
// Only the experimental warning, no access error
277-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
233+
expectDiagnosticEmpty(diagnostics);
278234
});
279235

280236
it("allows access to internal enum within the same project", async () => {
@@ -283,7 +239,7 @@ describe("access control", () => {
283239
model Consumer { x: Status }
284240
`);
285241

286-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
242+
expectDiagnosticEmpty(diagnostics);
287243
});
288244

289245
it("allows access to internal model across files in the same project", async () => {
@@ -297,7 +253,7 @@ describe("access control", () => {
297253
`,
298254
});
299255

300-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
256+
expectDiagnosticEmpty(diagnostics);
301257
});
302258

303259
it("allows access to internal op within the same project", async () => {
@@ -306,7 +262,7 @@ describe("access control", () => {
306262
op consumer is helper;
307263
`);
308264

309-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
265+
expectDiagnosticEmpty(diagnostics);
310266
});
311267

312268
it("allows access to internal scalar within the same project", async () => {
@@ -315,7 +271,7 @@ describe("access control", () => {
315271
model Consumer { x: MyScalar }
316272
`);
317273

318-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
274+
expectDiagnosticEmpty(diagnostics);
319275
});
320276

321277
it("allows access to internal alias within the same project", async () => {
@@ -324,7 +280,7 @@ describe("access control", () => {
324280
model Consumer { x: Shorthand }
325281
`);
326282

327-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
283+
expectDiagnosticEmpty(diagnostics);
328284
});
329285
});
330286

@@ -343,8 +299,7 @@ describe("access control", () => {
343299
model Consumer { x: Public }
344300
`);
345301

346-
// experimental-feature for InternalHelper in the library, no access error
347-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
302+
expectDiagnosticEmpty(diagnostics);
348303
});
349304
});
350305

@@ -361,7 +316,7 @@ describe("access control", () => {
361316
model Consumer { x: PublicModel }
362317
`);
363318

364-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
319+
expectDiagnosticEmpty(diagnostics);
365320
});
366321

367322
it("allows access to non-internal model in a namespace from another package", async () => {
@@ -378,7 +333,7 @@ describe("access control", () => {
378333
model Consumer { x: MyLib.PublicModel }
379334
`);
380335

381-
expectDiagnostics(diagnostics, { code: "experimental-feature" });
336+
expectDiagnosticEmpty(diagnostics);
382337
});
383338
});
384339
});

website/src/content/docs/docs/language-basics/access-modifiers.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,6 @@ The `internal` modifier can be combined with the `extern` modifier on decorator
9292
internal extern dec myInternalDecorator(target: unknown);
9393
```
9494

95-
### Suppressing the experimental warning
96-
97-
Since access modifiers are currently experimental, using `internal` will emit a warning. You can suppress this warning with a `#suppress` directive:
98-
99-
```typespec
100-
#suppress "experimental-feature"
101-
internal model MyInternalModel {}
102-
```
103-
10495
## Why not `namespace`?
10596

10697
The `internal` modifier is not supported on namespaces because namespaces in TypeSpec are **open and merged** across files. A namespace declared in one file can be extended in another file — potentially across library boundaries. Applying `internal` to a namespace would create ambiguity about which parts of the namespace are internal and which are public. Instead, mark individual declarations within a namespace as `internal`.

0 commit comments

Comments
 (0)