New scalar type (#1305)
This commit is contained in:
Родитель
6a55d933c7
Коммит
607962e49c
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/compiler",
|
||||
"comment": "**Breaking** Add new `scalar` type and updated all intrinsic types to be a scalar type. `model MyString is string` changes to `scalar MyString extends string`",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/compiler"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/openapi",
|
||||
"comment": "Uptake changes to compiler api with new `scalar` type",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/openapi"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/openapi3",
|
||||
"comment": "Uptake changes to compiler api to support Scalars",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/openapi3"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/rest",
|
||||
"comment": "Uptake changes to compiler api to support Scalars",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/rest"
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -19,6 +19,7 @@ import {
|
|||
ProjectionNode,
|
||||
ProjectionParameterDeclarationNode,
|
||||
ProjectionStatementNode,
|
||||
ScalarStatementNode,
|
||||
ScopeNode,
|
||||
Sym,
|
||||
SymbolFlags,
|
||||
|
@ -226,6 +227,9 @@ export function createBinder(program: Program): Binder {
|
|||
case SyntaxKind.ModelStatement:
|
||||
bindModelStatement(node);
|
||||
break;
|
||||
case SyntaxKind.ScalarStatement:
|
||||
bindScalarStatement(node);
|
||||
break;
|
||||
case SyntaxKind.InterfaceStatement:
|
||||
bindInterfaceStatement(node);
|
||||
break;
|
||||
|
@ -387,6 +391,12 @@ export function createBinder(program: Program): Binder {
|
|||
mutate(node).locals = new SymbolTable();
|
||||
}
|
||||
|
||||
function bindScalarStatement(node: ScalarStatementNode) {
|
||||
declareSymbol(node, SymbolFlags.Scalar);
|
||||
// Initialize locals for type parameters
|
||||
mutate(node).locals = new SymbolTable();
|
||||
}
|
||||
|
||||
function bindInterfaceStatement(node: InterfaceStatementNode) {
|
||||
declareSymbol(node, SymbolFlags.Interface);
|
||||
mutate(node).locals = new SymbolTable();
|
||||
|
|
|
@ -1,79 +1,60 @@
|
|||
import { getDeprecated } from "../lib/decorators.js";
|
||||
import { getDeprecated, getIndexer } from "../lib/decorators.js";
|
||||
import { createSymbol, createSymbolTable } from "./binder.js";
|
||||
import { compilerAssert, ProjectionError } from "./diagnostics.js";
|
||||
import { validateInheritanceDiscriminatedUnions } from "./helpers/discriminator-utils.js";
|
||||
import {
|
||||
AugmentDecoratorStatementNode,
|
||||
DecoratedType,
|
||||
Decorator,
|
||||
DecoratorContext,
|
||||
DecoratorDeclarationStatementNode,
|
||||
Diagnostic,
|
||||
DiagnosticTarget,
|
||||
Expression,
|
||||
FunctionDeclarationStatementNode,
|
||||
FunctionParameter,
|
||||
FunctionParameterNode,
|
||||
getIndexer,
|
||||
getIntrinsicModelName,
|
||||
getParentTemplateNode,
|
||||
IdentifierKind,
|
||||
IntrinsicModelName,
|
||||
isIntrinsic,
|
||||
isNeverType,
|
||||
isUnknownType,
|
||||
isVoidType,
|
||||
JsSourceFileNode,
|
||||
MarshalledValue,
|
||||
ModelIndexer,
|
||||
ModelKeyIndexer,
|
||||
ModelSpreadPropertyNode,
|
||||
ModifierFlags,
|
||||
NeverIndexer,
|
||||
NeverType,
|
||||
ProjectedProgram,
|
||||
ProjectionModelExpressionNode,
|
||||
ProjectionModelPropertyNode,
|
||||
ProjectionModelSpreadPropertyNode,
|
||||
SymbolFlags,
|
||||
TemplateableNode,
|
||||
TemplateParameter,
|
||||
UnknownType,
|
||||
VoidType,
|
||||
} from "./index.js";
|
||||
import { createDiagnostic } from "./messages.js";
|
||||
import { getIdentifierContext, hasParseError, visitChildren } from "./parser.js";
|
||||
import { Program } from "./program.js";
|
||||
import { Program, ProjectedProgram } from "./program.js";
|
||||
import { createProjectionMembers } from "./projection-members.js";
|
||||
import { getParentTemplateNode, isNeverType, isUnknownType, isVoidType } from "./type-utils.js";
|
||||
import {
|
||||
AliasStatementNode,
|
||||
ArrayExpressionNode,
|
||||
AugmentDecoratorStatementNode,
|
||||
BooleanLiteral,
|
||||
BooleanLiteralNode,
|
||||
CadlScriptNode,
|
||||
DecoratedType,
|
||||
Decorator,
|
||||
DecoratorApplication,
|
||||
DecoratorArgument,
|
||||
DecoratorContext,
|
||||
DecoratorDeclarationStatementNode,
|
||||
DecoratorExpressionNode,
|
||||
Diagnostic,
|
||||
DiagnosticTarget,
|
||||
Enum,
|
||||
EnumMember,
|
||||
EnumMemberNode,
|
||||
EnumStatementNode,
|
||||
ErrorType,
|
||||
Expression,
|
||||
FunctionDeclarationStatementNode,
|
||||
FunctionParameter,
|
||||
FunctionParameterNode,
|
||||
FunctionType,
|
||||
IdentifierKind,
|
||||
IdentifierNode,
|
||||
Interface,
|
||||
InterfaceStatementNode,
|
||||
IntersectionExpressionNode,
|
||||
IntrinsicScalarName,
|
||||
JsSourceFileNode,
|
||||
LiteralNode,
|
||||
LiteralType,
|
||||
MarshalledValue,
|
||||
MemberExpressionNode,
|
||||
Model,
|
||||
ModelExpressionNode,
|
||||
ModelIndexer,
|
||||
ModelProperty,
|
||||
ModelPropertyNode,
|
||||
ModelSpreadPropertyNode,
|
||||
ModelStatementNode,
|
||||
ModifierFlags,
|
||||
Namespace,
|
||||
NamespaceStatementNode,
|
||||
NeverType,
|
||||
Node,
|
||||
NodeFlags,
|
||||
NumericLiteral,
|
||||
|
@ -91,6 +72,9 @@ import {
|
|||
ProjectionIfExpressionNode,
|
||||
ProjectionLambdaExpressionNode,
|
||||
ProjectionMemberExpressionNode,
|
||||
ProjectionModelExpressionNode,
|
||||
ProjectionModelPropertyNode,
|
||||
ProjectionModelSpreadPropertyNode,
|
||||
ProjectionNode,
|
||||
ProjectionRelationalExpressionNode,
|
||||
ProjectionStatementItem,
|
||||
|
@ -98,13 +82,18 @@ import {
|
|||
ProjectionUnaryExpressionNode,
|
||||
ReturnExpressionNode,
|
||||
ReturnRecord,
|
||||
Scalar,
|
||||
ScalarStatementNode,
|
||||
StringLiteral,
|
||||
StringLiteralNode,
|
||||
Sym,
|
||||
SymbolFlags,
|
||||
SymbolLinks,
|
||||
SymbolTable,
|
||||
SyntaxKind,
|
||||
TemplateableNode,
|
||||
TemplateDeclarationNode,
|
||||
TemplateParameter,
|
||||
TemplateParameterDeclarationNode,
|
||||
Tuple,
|
||||
TupleExpressionNode,
|
||||
|
@ -117,6 +106,8 @@ import {
|
|||
UnionStatementNode,
|
||||
UnionVariant,
|
||||
UnionVariantNode,
|
||||
UnknownType,
|
||||
VoidType,
|
||||
} from "./types.js";
|
||||
import { isArray, MultiKeyMap, Mutable, mutate } from "./util.js";
|
||||
|
||||
|
@ -186,7 +177,17 @@ export interface Checker {
|
|||
* @param type Type to check
|
||||
* @param stdType If provided check is that standard type
|
||||
*/
|
||||
isStdType(type: Type, stdType?: IntrinsicModelName | "Array" | "Record"): boolean;
|
||||
isStdType(
|
||||
type: Scalar,
|
||||
stdType?: IntrinsicScalarName
|
||||
): type is Scalar & { name: IntrinsicScalarName };
|
||||
isStdType(type: Type, stdType?: StdTypeName): type is Type & { name: StdTypeName };
|
||||
|
||||
/**
|
||||
* Std type
|
||||
* @param name Name
|
||||
*/
|
||||
getStdType<T extends keyof StdTypes>(name: T): StdTypes[T];
|
||||
|
||||
/**
|
||||
* Check and resolve a type for the given type reference node.
|
||||
|
@ -222,13 +223,18 @@ const TypeInstantiationMap = class
|
|||
extends MultiKeyMap<readonly Type[], Type>
|
||||
implements TypeInstantiationMap {};
|
||||
|
||||
type StdTypeName = IntrinsicModelName | "Array" | "Record";
|
||||
type StdTypeName = IntrinsicScalarName | "Array" | "Record";
|
||||
type StdTypes = {
|
||||
// Models
|
||||
Array: Model;
|
||||
Record: Model;
|
||||
} & Record<IntrinsicScalarName, Scalar>;
|
||||
type ReflectionTypeName = keyof typeof ReflectionNameToKind;
|
||||
|
||||
let currentSymbolId = 0;
|
||||
|
||||
export function createChecker(program: Program): Checker {
|
||||
const stdTypes: Partial<Record<StdTypeName, Model>> = {};
|
||||
const stdTypes: Partial<StdTypes> = {};
|
||||
const symbolLinks = new Map<number, SymbolLinks>();
|
||||
const mergedSymbols = new Map<Sym, Sym>();
|
||||
const augmentDecoratorsForSym = new Map<Sym, AugmentDecoratorStatementNode[]>();
|
||||
|
@ -255,6 +261,8 @@ export function createChecker(program: Program): Checker {
|
|||
const voidType = createType({ kind: "Intrinsic", name: "void" } as const);
|
||||
const neverType = createType({ kind: "Intrinsic", name: "never" } as const);
|
||||
const unknownType = createType({ kind: "Intrinsic", name: "unknown" } as const);
|
||||
const nullType = createType({ kind: "Intrinsic", name: "null" } as const);
|
||||
const nullSym = createSymbol(undefined, "null", SymbolFlags.None);
|
||||
|
||||
const projectionsByTypeKind = new Map<Type["kind"], ProjectionStatementNode[]>([
|
||||
["Model", []],
|
||||
|
@ -331,6 +339,7 @@ export function createChecker(program: Program): Checker {
|
|||
finishType,
|
||||
isTypeAssignableTo,
|
||||
isStdType,
|
||||
getStdType,
|
||||
resolveTypeReference,
|
||||
};
|
||||
|
||||
|
@ -355,21 +364,30 @@ export function createChecker(program: Program): Checker {
|
|||
},
|
||||
declarations: [],
|
||||
});
|
||||
|
||||
// Until we have an `unit` type for `null`
|
||||
mutate(cadlNamespaceBinding!.exports).set("null", nullSym);
|
||||
mutate(nullSym).type = nullType;
|
||||
getSymbolLinks(nullSym).type = nullType;
|
||||
}
|
||||
|
||||
function getStdType<T extends StdTypeName>(name: T): Model & { name: T } {
|
||||
function getStdType<T extends keyof StdTypes>(name: T): StdTypes[T] {
|
||||
const type = stdTypes[name];
|
||||
if (type !== undefined) {
|
||||
return type as any;
|
||||
}
|
||||
|
||||
const sym = cadlNamespaceBinding?.exports?.get(name);
|
||||
checkModelStatement(sym!.declarations[0] as any, undefined);
|
||||
if (sym && sym.flags & SymbolFlags.Model) {
|
||||
checkModelStatement(sym!.declarations[0] as any, undefined);
|
||||
} else {
|
||||
checkScalar(sym!.declarations[0] as any, undefined);
|
||||
}
|
||||
|
||||
const loadedType = stdTypes[name];
|
||||
compilerAssert(
|
||||
loadedType,
|
||||
"Cadl built-in array type should have been initalized before using array syntax."
|
||||
`Cadl std type "${name}" should have been initalized before using array syntax.`
|
||||
);
|
||||
return loadedType as any;
|
||||
}
|
||||
|
@ -505,6 +523,8 @@ export function createChecker(program: Program): Checker {
|
|||
return checkModel(node, mapper);
|
||||
case SyntaxKind.ModelProperty:
|
||||
return checkModelProperty(node, mapper);
|
||||
case SyntaxKind.ScalarStatement:
|
||||
return checkScalar(node, mapper);
|
||||
case SyntaxKind.AliasStatement:
|
||||
return checkAlias(node, mapper);
|
||||
case SyntaxKind.EnumStatement:
|
||||
|
@ -562,6 +582,8 @@ export function createChecker(program: Program): Checker {
|
|||
return getNamespaceString(type, options);
|
||||
case "TemplateParameter":
|
||||
return type.node.id.sv;
|
||||
case "Scalar":
|
||||
return getScalarName(type, options);
|
||||
case "Model":
|
||||
return getModelName(type, options);
|
||||
case "ModelProperty":
|
||||
|
@ -618,12 +640,18 @@ export function createChecker(program: Program): Checker {
|
|||
return nsName ? `${nsName}.${e.name}` : e.name;
|
||||
}
|
||||
|
||||
function getScalarName(scalar: Scalar, options: TypeNameOptions | undefined): string {
|
||||
const nsName = getNamespaceString(scalar.namespace, options);
|
||||
return nsName ? `${nsName}.${scalar.name}` : scalar.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a fully qualified id of node
|
||||
*/
|
||||
function getNodeSymId(
|
||||
node:
|
||||
| ModelStatementNode
|
||||
| ScalarStatementNode
|
||||
| AliasStatementNode
|
||||
| InterfaceStatementNode
|
||||
| OperationStatementNode
|
||||
|
@ -658,7 +686,7 @@ export function createChecker(program: Program): Checker {
|
|||
if (model.name === "" && model.properties.size === 0) {
|
||||
return "{}";
|
||||
}
|
||||
if (model.indexer && model.indexer.key.kind === "Model") {
|
||||
if (model.indexer && model.indexer.key.kind === "Scalar") {
|
||||
if (model.name === "Array" && isInCadlNamespace(model)) {
|
||||
return `${getTypeName(model.indexer.value!, options)}[]`;
|
||||
}
|
||||
|
@ -702,12 +730,7 @@ export function createChecker(program: Program): Checker {
|
|||
node: TemplateParameterDeclarationNode,
|
||||
mapper: TypeMapper | undefined
|
||||
): Type {
|
||||
const parentNode = node.parent! as
|
||||
| ModelStatementNode
|
||||
| InterfaceStatementNode
|
||||
| OperationStatementNode
|
||||
| UnionStatementNode
|
||||
| AliasStatementNode;
|
||||
const parentNode = node.parent! as TemplateableNode;
|
||||
const links = getSymbolLinks(node.symbol);
|
||||
|
||||
let type: TemplateParameter | undefined = links.declaredType as TemplateParameter;
|
||||
|
@ -950,17 +973,13 @@ export function createChecker(program: Program): Checker {
|
|||
if (
|
||||
sym.flags &
|
||||
(SymbolFlags.Model |
|
||||
SymbolFlags.Scalar |
|
||||
SymbolFlags.Alias |
|
||||
SymbolFlags.Interface |
|
||||
SymbolFlags.Operation |
|
||||
SymbolFlags.Union)
|
||||
) {
|
||||
const decl = sym.declarations[0] as
|
||||
| ModelStatementNode
|
||||
| AliasStatementNode
|
||||
| InterfaceStatementNode
|
||||
| OperationStatementNode
|
||||
| UnionStatementNode;
|
||||
const decl = sym.declarations[0] as TemplateableNode;
|
||||
if (decl.templateParameters.length === 0) {
|
||||
if (args.length > 0) {
|
||||
reportCheckerDiagnostic(
|
||||
|
@ -978,6 +997,8 @@ export function createChecker(program: Program): Checker {
|
|||
baseType =
|
||||
sym.flags & SymbolFlags.Model
|
||||
? checkModelStatement(decl as ModelStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Scalar
|
||||
? checkScalar(decl as ScalarStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Alias
|
||||
? checkAlias(decl as AliasStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Interface
|
||||
|
@ -993,6 +1014,8 @@ export function createChecker(program: Program): Checker {
|
|||
// we haven't checked the declared type yet, so do so.
|
||||
sym.flags & SymbolFlags.Model
|
||||
? checkModelStatement(decl as ModelStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Scalar
|
||||
? checkScalar(decl as ScalarStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Alias
|
||||
? checkAlias(decl as AliasStatementNode, mapper)
|
||||
: sym.flags & SymbolFlags.Interface
|
||||
|
@ -1286,7 +1309,7 @@ export function createChecker(program: Program): Checker {
|
|||
derivedModels: [],
|
||||
});
|
||||
|
||||
const indexers: ModelKeyIndexer[] = [];
|
||||
const indexers: ModelIndexer[] = [];
|
||||
for (const [optionNode, option] of options) {
|
||||
if (option.kind === "TemplateParameter") {
|
||||
continue;
|
||||
|
@ -1299,15 +1322,7 @@ export function createChecker(program: Program): Checker {
|
|||
}
|
||||
|
||||
if (option.indexer) {
|
||||
if (isNeverIndexer(option.indexer)) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "intersect-invalid-index",
|
||||
messageId: "never",
|
||||
target: optionNode,
|
||||
})
|
||||
);
|
||||
} else if (option.indexer.key.name === "integer") {
|
||||
if (option.indexer.key.name === "integer") {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "intersect-invalid-index",
|
||||
|
@ -1396,6 +1411,7 @@ export function createChecker(program: Program): Checker {
|
|||
namespace,
|
||||
node,
|
||||
models: new Map(),
|
||||
scalars: new Map(),
|
||||
operations: new Map(),
|
||||
namespaces: new Map(),
|
||||
interfaces: new Map(),
|
||||
|
@ -1423,6 +1439,7 @@ export function createChecker(program: Program): Checker {
|
|||
function getParentNamespaceType(
|
||||
node:
|
||||
| ModelStatementNode
|
||||
| ScalarStatementNode
|
||||
| NamespaceStatementNode
|
||||
| OperationStatementNode
|
||||
| EnumStatementNode
|
||||
|
@ -1444,6 +1461,7 @@ export function createChecker(program: Program): Checker {
|
|||
while (parent !== undefined) {
|
||||
if (
|
||||
parent.kind === SyntaxKind.ModelStatement ||
|
||||
parent.kind === SyntaxKind.ScalarStatement ||
|
||||
parent.kind === SyntaxKind.OperationStatement ||
|
||||
parent.kind === SyntaxKind.EnumStatement ||
|
||||
parent.kind === SyntaxKind.InterfaceStatement ||
|
||||
|
@ -2224,11 +2242,6 @@ export function createChecker(program: Program): Checker {
|
|||
}
|
||||
if (indexer) {
|
||||
type.indexer = indexer;
|
||||
} else {
|
||||
const intrinsicModelName = getIntrinsicModelName(program, type);
|
||||
if (intrinsicModelName) {
|
||||
type.indexer = { key: neverType, value: undefined };
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
@ -2275,24 +2288,14 @@ export function createChecker(program: Program): Checker {
|
|||
return;
|
||||
}
|
||||
|
||||
if (isNeverIndexer(parentModel.indexer)) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "no-prop",
|
||||
format: { propName: property.name },
|
||||
target: diagnosticTarget,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const [valid, diagnostics] = isTypeAssignableTo(
|
||||
property.type,
|
||||
parentModel.indexer.value!,
|
||||
diagnosticTarget.kind === SyntaxKind.ModelSpreadProperty
|
||||
? diagnosticTarget
|
||||
: diagnosticTarget.value
|
||||
);
|
||||
if (!valid) reportCheckerDiagnostics(diagnostics);
|
||||
}
|
||||
const [valid, diagnostics] = isTypeAssignableTo(
|
||||
property.type,
|
||||
parentModel.indexer.value!,
|
||||
diagnosticTarget.kind === SyntaxKind.ModelSpreadProperty
|
||||
? diagnosticTarget
|
||||
: diagnosticTarget.value
|
||||
);
|
||||
if (!valid) reportCheckerDiagnostics(diagnostics);
|
||||
}
|
||||
|
||||
function checkModelProperties(
|
||||
|
@ -2337,11 +2340,11 @@ export function createChecker(program: Program): Checker {
|
|||
const overriddenProp = getOverriddenProperty(newProp);
|
||||
if (overriddenProp) {
|
||||
const [isAssignable, _] = isTypeAssignableTo(newProp.type, overriddenProp.type, newProp);
|
||||
const parentIntrinsic = isIntrinsic(program, overriddenProp.type);
|
||||
const parentScalar = overriddenProp.type.kind === "Scalar";
|
||||
const parentType = getTypeName(overriddenProp.type);
|
||||
const newPropType = getTypeName(newProp.type);
|
||||
|
||||
if (!parentIntrinsic) {
|
||||
if (!parentScalar) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "override-property-intrinsic",
|
||||
|
@ -2501,19 +2504,6 @@ export function createChecker(program: Program): Checker {
|
|||
);
|
||||
}
|
||||
|
||||
if (isIntrinsic(program, heritageType)) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "extend-primitive",
|
||||
target: heritageRef,
|
||||
format: {
|
||||
modelName: model.id.sv,
|
||||
baseModelName: heritageType.name,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return heritageType;
|
||||
}
|
||||
|
||||
|
@ -2592,13 +2582,6 @@ export function createChecker(program: Program): Checker {
|
|||
return [];
|
||||
}
|
||||
|
||||
if (targetType.indexer && isNeverIndexer(targetType.indexer)) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({ code: "spread-model", messageId: "neverIndex", target: targetNode })
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const props: ModelProperty[] = [];
|
||||
// copy each property
|
||||
for (const prop of walkPropertiesInherited(targetType)) {
|
||||
|
@ -2856,6 +2839,87 @@ export function createChecker(program: Program): Checker {
|
|||
});
|
||||
}
|
||||
|
||||
function checkScalar(node: ScalarStatementNode, mapper: TypeMapper | undefined): Scalar {
|
||||
const links = getSymbolLinks(node.symbol);
|
||||
|
||||
if (links.declaredType && mapper === undefined) {
|
||||
// we're not instantiating this model and we've already checked it
|
||||
return links.declaredType as any;
|
||||
}
|
||||
const decorators: DecoratorApplication[] = [];
|
||||
|
||||
const type: Scalar = createType({
|
||||
kind: "Scalar",
|
||||
name: node.id.sv,
|
||||
node: node,
|
||||
namespace: getParentNamespaceType(node),
|
||||
decorators,
|
||||
derivedScalars: [],
|
||||
});
|
||||
linkType(links, type, mapper);
|
||||
|
||||
if (node.extends) {
|
||||
type.baseScalar = checkScalarExtends(node, node.extends, mapper);
|
||||
if (type.baseScalar) {
|
||||
checkDeprecated(type.baseScalar, node.extends);
|
||||
type.baseScalar.derivedScalars.push(type);
|
||||
}
|
||||
}
|
||||
decorators.push(...checkDecorators(type, node, mapper));
|
||||
|
||||
if (mapper === undefined) {
|
||||
type.namespace?.scalars.set(type.name, type);
|
||||
}
|
||||
if (shouldCreateTypeForTemplate(node, mapper)) {
|
||||
finishType(type, mapper);
|
||||
}
|
||||
if (isInCadlNamespace(type)) {
|
||||
stdTypes[type.name as any as keyof StdTypes] = type as any;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
function checkScalarExtends(
|
||||
scalar: ScalarStatementNode,
|
||||
extendsRef: TypeReferenceNode,
|
||||
mapper: TypeMapper | undefined
|
||||
): Scalar | undefined {
|
||||
const symId = getNodeSymId(scalar);
|
||||
pendingResolutions.add(symId);
|
||||
|
||||
const target = resolveTypeReferenceSym(extendsRef, mapper);
|
||||
if (target === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (pendingResolutions.has(getNodeSymId(target.declarations[0] as any))) {
|
||||
if (mapper === undefined) {
|
||||
reportCheckerDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "circular-base-type",
|
||||
format: { typeName: (target.declarations[0] as any).id.sv },
|
||||
target: target,
|
||||
})
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const extendsType = checkTypeReferenceSymbol(target, extendsRef, mapper);
|
||||
pendingResolutions.delete(symId);
|
||||
if (isErrorType(extendsType)) {
|
||||
compilerAssert(program.hasError(), "Should already have reported an error.", extendsRef);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (extendsType.kind !== "Scalar") {
|
||||
reportCheckerDiagnostic(createDiagnostic({ code: "extend-model", target: extendsRef }));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return extendsType;
|
||||
}
|
||||
|
||||
function checkAlias(node: AliasStatementNode, mapper: TypeMapper | undefined): Type {
|
||||
const links = getSymbolLinks(node.symbol);
|
||||
|
||||
|
@ -3317,6 +3381,7 @@ export function createChecker(program: Program): Checker {
|
|||
name: "",
|
||||
node: globalNamespaceNode,
|
||||
models: new Map(),
|
||||
scalars: new Map(),
|
||||
operations: new Map(),
|
||||
namespaces: new Map(),
|
||||
interfaces: new Map(),
|
||||
|
@ -4218,6 +4283,38 @@ export function createChecker(program: Program): Checker {
|
|||
);
|
||||
}
|
||||
|
||||
function isRelatedToScalar(source: Type, target: Scalar): boolean | undefined {
|
||||
switch (source.kind) {
|
||||
case "Number":
|
||||
return (
|
||||
areScalarsRelated(target, getStdType("numeric")) &&
|
||||
isNumericLiteralRelatedTo(source, target.name as any)
|
||||
);
|
||||
case "String":
|
||||
return areScalarsRelated(target, getStdType("string"));
|
||||
case "Boolean":
|
||||
return areScalarsRelated(target, getStdType("boolean"));
|
||||
case "Scalar":
|
||||
return areScalarsRelated(source, target);
|
||||
case "Union":
|
||||
return undefined;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function areScalarsRelated(source: Scalar, target: Scalar) {
|
||||
let current: Scalar | undefined = source;
|
||||
while (current) {
|
||||
if (current === target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
current = current.baseScalar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSimpleTypeAssignableTo(source: Type, target: Type): boolean | undefined {
|
||||
if (isVoidType(target) || isNeverType(target)) return false;
|
||||
if (isUnknownType(target)) return true;
|
||||
|
@ -4225,34 +4322,11 @@ export function createChecker(program: Program): Checker {
|
|||
return source.kind === ReflectionNameToKind[target.name];
|
||||
}
|
||||
|
||||
const sourceIntrinsicName = getIntrinsicModelName(program, source);
|
||||
const targetIntrinsicName = getIntrinsicModelName(program, target);
|
||||
if (targetIntrinsicName) {
|
||||
switch (source.kind) {
|
||||
case "Number":
|
||||
return (
|
||||
IntrinsicTypeRelations.isAssignable(targetIntrinsicName, "numeric") &&
|
||||
isNumericLiteralRelatedTo(source, targetIntrinsicName as any)
|
||||
);
|
||||
case "String":
|
||||
return IntrinsicTypeRelations.isAssignable("string", targetIntrinsicName);
|
||||
case "Boolean":
|
||||
return IntrinsicTypeRelations.isAssignable("boolean", targetIntrinsicName);
|
||||
case "Union":
|
||||
return undefined;
|
||||
case "Model":
|
||||
if (!sourceIntrinsicName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sourceIntrinsicName) {
|
||||
return false;
|
||||
}
|
||||
return IntrinsicTypeRelations.isAssignable(sourceIntrinsicName, targetIntrinsicName);
|
||||
if (target.kind === "Scalar") {
|
||||
return isRelatedToScalar(source, target);
|
||||
}
|
||||
|
||||
if (sourceIntrinsicName && target.kind === "Model") {
|
||||
if (source.kind === "Scalar" && target.kind === "Model") {
|
||||
return false;
|
||||
}
|
||||
if (target.kind === "String") {
|
||||
|
@ -4339,11 +4413,6 @@ export function createChecker(program: Program): Checker {
|
|||
target: Model & { indexer: ModelIndexer },
|
||||
diagnosticTarget: DiagnosticTarget
|
||||
): [boolean, Diagnostic[]] {
|
||||
if (isNeverIndexer(target.indexer)) {
|
||||
// TODO better error here saying that you cannot assign to
|
||||
return [false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
|
||||
}
|
||||
|
||||
// Model expressions should be able to be assigned.
|
||||
if (source.name === "") {
|
||||
return isIndexConstraintValid(target.indexer.value, source, diagnosticTarget);
|
||||
|
@ -4481,10 +4550,19 @@ export function createChecker(program: Program): Checker {
|
|||
});
|
||||
}
|
||||
|
||||
function isStdType(type: Type, stdType?: StdTypeName): boolean {
|
||||
if (type.kind !== "Model") return false;
|
||||
const intrinsicModelName = getIntrinsicModelName(program, type);
|
||||
if (intrinsicModelName) return stdType === undefined || stdType === intrinsicModelName;
|
||||
function isStdType(
|
||||
type: Scalar,
|
||||
stdType?: IntrinsicScalarName
|
||||
): type is Scalar & { name: IntrinsicScalarName };
|
||||
function isStdType(type: Type, stdType?: StdTypeName): type is Type & { name: StdTypeName } {
|
||||
type = type.projectionBase ?? type;
|
||||
if (
|
||||
(type.kind !== "Model" && type.kind !== "Scalar") ||
|
||||
type.namespace === undefined ||
|
||||
!isCadlNamespace(type.namespace)
|
||||
)
|
||||
return false;
|
||||
if (type.kind === "Scalar") return stdType === undefined || stdType === type.name;
|
||||
if (stdType === "Array" && type === stdTypes["Array"]) return true;
|
||||
if (stdType === "Record" && type === stdTypes["Record"]) return true;
|
||||
return false;
|
||||
|
@ -4513,58 +4591,6 @@ const numericRanges = {
|
|||
float64: [Number.MIN_VALUE, Number.MAX_VALUE, { int: false }],
|
||||
} as const;
|
||||
|
||||
class IntrinsicTypeRelationTree<
|
||||
T extends Record<IntrinsicModelName, IntrinsicModelName | IntrinsicModelName[] | "unknown">
|
||||
> {
|
||||
private map = new Map<IntrinsicModelName, Set<IntrinsicModelName | "unknown">>();
|
||||
public constructor(data: T) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parents = Array.isArray(value) ? value : [value];
|
||||
const set = new Set<IntrinsicModelName | "unknown">([
|
||||
key as IntrinsicModelName,
|
||||
...parents,
|
||||
...parents.flatMap((parent) => [...(this.map.get(parent as any) ?? [])]),
|
||||
]);
|
||||
this.map.set(key as IntrinsicModelName, set);
|
||||
}
|
||||
}
|
||||
|
||||
public isAssignable(source: IntrinsicModelName, target: IntrinsicModelName) {
|
||||
return this.map.get(source)?.has(target);
|
||||
}
|
||||
}
|
||||
|
||||
const IntrinsicTypeRelations = new IntrinsicTypeRelationTree({
|
||||
Record: "unknown",
|
||||
bytes: "unknown",
|
||||
numeric: "unknown",
|
||||
integer: "numeric",
|
||||
float: "numeric",
|
||||
int64: "integer",
|
||||
safeint: "int64",
|
||||
int32: "safeint",
|
||||
int16: "int32",
|
||||
int8: "int16",
|
||||
uint64: "integer",
|
||||
uint32: "uint64",
|
||||
uint16: "uint32",
|
||||
uint8: "uint16",
|
||||
float64: "float",
|
||||
float32: "float64",
|
||||
string: "unknown",
|
||||
plainDate: "unknown",
|
||||
plainTime: "unknown",
|
||||
zonedDateTime: "unknown",
|
||||
duration: "unknown",
|
||||
boolean: "unknown",
|
||||
null: "unknown",
|
||||
Map: "unknown",
|
||||
});
|
||||
|
||||
/**
|
||||
* Find all named models that could have been the source of the given
|
||||
* property. This includes the named parents of all property sources in a
|
||||
|
@ -4602,10 +4628,6 @@ function addDerivedModels(models: Set<Model>, possiblyDerivedModels: ReadonlySet
|
|||
}
|
||||
}
|
||||
|
||||
export function isNeverIndexer(indexer: ModelIndexer): indexer is NeverIndexer {
|
||||
return isNeverType(indexer.key);
|
||||
}
|
||||
|
||||
interface TypeMapper {
|
||||
getMappedType(type: TemplateParameter): Type;
|
||||
args: readonly Type[];
|
||||
|
|
|
@ -22,12 +22,11 @@ import { compile, Program } from "../core/program.js";
|
|||
import { initCadlProject } from "../init/index.js";
|
||||
import { compilerAssert, logDiagnostics } from "./diagnostics.js";
|
||||
import { findUnformattedCadlFiles, formatCadlFiles } from "./formatter-fs.js";
|
||||
import { CompilerHost } from "./index.js";
|
||||
import { installCadlDependencies } from "./install.js";
|
||||
import { createConsoleSink } from "./logger/index.js";
|
||||
import { NodeHost } from "./node-host.js";
|
||||
import { getAnyExtensionFromPath, getBaseFileName, joinPaths, resolvePath } from "./path-utils.js";
|
||||
import { Diagnostic } from "./types.js";
|
||||
import { CompilerHost, Diagnostic } from "./types.js";
|
||||
import { cadlVersion, ExternalError } from "./util.js";
|
||||
|
||||
async function main() {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { getIntrinsicModelName, getPropertyType } from "../lib/decorators.js";
|
||||
import { DecoratorContext } from "./index.js";
|
||||
import { getPropertyType } from "./index.js";
|
||||
import { createDiagnostic, reportDiagnostic } from "./messages.js";
|
||||
import { Program } from "./program.js";
|
||||
import {
|
||||
DecoratorContext,
|
||||
Diagnostic,
|
||||
DiagnosticTarget,
|
||||
IntrinsicModelName,
|
||||
Model,
|
||||
IntrinsicScalarName,
|
||||
ModelProperty,
|
||||
Scalar,
|
||||
Type,
|
||||
} from "./types.js";
|
||||
|
||||
|
@ -54,25 +54,23 @@ export function validateDecoratorTarget<K extends TypeKind>(
|
|||
|
||||
export function validateDecoratorTargetIntrinsic(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
decoratorName: string,
|
||||
expectedType: IntrinsicModelName | IntrinsicModelName[]
|
||||
expectedType: IntrinsicScalarName | IntrinsicScalarName[]
|
||||
): boolean {
|
||||
const actualType = getIntrinsicModelName(context.program, getPropertyType(target));
|
||||
const isCorrect =
|
||||
actualType &&
|
||||
(typeof expectedType === "string"
|
||||
? actualType === expectedType
|
||||
: expectedType.includes(actualType));
|
||||
const expectedTypeStrs = typeof expectedType === "string" ? [expectedType] : expectedType;
|
||||
const expectedTypes = expectedTypeStrs.map((x) => context.program.checker.getStdType(x));
|
||||
const type = getPropertyType(target);
|
||||
const isCorrect = expectedTypes.some(
|
||||
(x) => context.program.checker.isTypeAssignableTo(type.projectionBase ?? type, x, type)[0]
|
||||
);
|
||||
if (!isCorrect) {
|
||||
context.program.reportDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "decorator-wrong-target",
|
||||
format: {
|
||||
decorator: decoratorName,
|
||||
to: `type it is not one of: ${
|
||||
typeof expectedType === "string" ? expectedType : expectedType.join(", ")
|
||||
}`,
|
||||
to: `type it is not one of: ${typeof expectedTypeStrs.join(", ")}`,
|
||||
},
|
||||
target: context.decoratorTarget,
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Discriminator } from "../../lib/decorators.js";
|
||||
import { getDiscriminatedTypes, Program } from "../index.js";
|
||||
import { Discriminator, getDiscriminatedTypes } from "../../lib/decorators.js";
|
||||
import { createDiagnostic } from "../messages.js";
|
||||
import { Program } from "../program.js";
|
||||
import { isTemplateDeclarationOrInstance } from "../type-utils.js";
|
||||
import { Diagnostic, Model, Type, Union } from "../types.js";
|
||||
import { DuplicateTracker, isDefined } from "../util.js";
|
||||
|
|
|
@ -309,6 +309,12 @@ const diagnostics = {
|
|||
default: paramMessage`Model has an inherited property named ${"propName"} of type ${"propType"} which can only override an intrinsic type on the parent property, not ${"parentType"}`,
|
||||
},
|
||||
},
|
||||
"extend-scalar": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
default: "Scalar must extend other scalars.",
|
||||
},
|
||||
},
|
||||
"extend-model": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
|
@ -316,12 +322,6 @@ const diagnostics = {
|
|||
modelExpression: "Models cannot extend model expressions.",
|
||||
},
|
||||
},
|
||||
"extend-primitive": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
default: paramMessage`Cannot extend primitive types. Use 'model ${"modelName"} is ${"baseModelName"}' instead.`,
|
||||
},
|
||||
},
|
||||
"is-model": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
|
@ -687,7 +687,7 @@ const diagnostics = {
|
|||
"circular-base-type": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
default: paramMessage`Model type '${"typeName"}' recursively references itself as a base type.`,
|
||||
default: paramMessage`Type '${"typeName"}' recursively references itself as a base type.`,
|
||||
},
|
||||
},
|
||||
"circular-op-signature": {
|
||||
|
|
|
@ -81,6 +81,7 @@ import {
|
|||
ProjectionStatementNode,
|
||||
ProjectionTupleExpressionNode,
|
||||
ProjectionUnionSelectorNode,
|
||||
ScalarStatementNode,
|
||||
SourceFile,
|
||||
Statement,
|
||||
StringLiteralNode,
|
||||
|
@ -353,6 +354,9 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
|
|||
case Token.ModelKeyword:
|
||||
item = parseModelStatement(pos, decorators);
|
||||
break;
|
||||
case Token.ScalarKeyword:
|
||||
item = parseScalarStatement(pos, decorators);
|
||||
break;
|
||||
case Token.NamespaceKeyword:
|
||||
item = parseNamespaceStatement(pos, decorators);
|
||||
break;
|
||||
|
@ -852,6 +856,33 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa
|
|||
};
|
||||
}
|
||||
|
||||
function parseScalarStatement(
|
||||
pos: number,
|
||||
decorators: DecoratorExpressionNode[]
|
||||
): ScalarStatementNode {
|
||||
parseExpected(Token.ScalarKeyword);
|
||||
const id = parseIdentifier();
|
||||
const templateParameters = parseTemplateParameterList();
|
||||
|
||||
const optionalExtends = parseOptionalScalarExtends();
|
||||
|
||||
return {
|
||||
kind: SyntaxKind.ScalarStatement,
|
||||
id,
|
||||
templateParameters,
|
||||
extends: optionalExtends,
|
||||
decorators,
|
||||
...finishNode(pos),
|
||||
};
|
||||
}
|
||||
|
||||
function parseOptionalScalarExtends() {
|
||||
if (parseOptional(Token.ExtendsKeyword)) {
|
||||
return parseReferenceExpression();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseEnumStatement(
|
||||
pos: number,
|
||||
decorators: DecoratorExpressionNode[]
|
||||
|
@ -2754,6 +2785,13 @@ export function visitChildren<T>(node: Node, cb: NodeCallback<T>): T | undefined
|
|||
visitNode(cb, node.is) ||
|
||||
visitEach(cb, node.properties)
|
||||
);
|
||||
case SyntaxKind.ScalarStatement:
|
||||
return (
|
||||
visitEach(cb, node.decorators) ||
|
||||
visitNode(cb, node.id) ||
|
||||
visitEach(cb, node.templateParameters) ||
|
||||
visitNode(cb, node.extends)
|
||||
);
|
||||
case SyntaxKind.UnionStatement:
|
||||
return (
|
||||
visitEach(cb, node.decorators) ||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { isNeverType } from "../lib/decorators.js";
|
||||
import { finishTypeForProgram } from "./checker.js";
|
||||
import { compilerAssert } from "./diagnostics.js";
|
||||
import {
|
||||
createStateAccessors,
|
||||
getParentTemplateNode,
|
||||
isNeverIndexer,
|
||||
isNeverType,
|
||||
isProjectedProgram,
|
||||
isTemplateInstance,
|
||||
ProjectedProgram,
|
||||
Scalar,
|
||||
} from "./index.js";
|
||||
import { Program } from "./program.js";
|
||||
import {
|
||||
|
@ -114,6 +114,9 @@ export function createProjector(
|
|||
);
|
||||
projected = projectNamespace(type, false);
|
||||
break;
|
||||
case "Scalar":
|
||||
projected = projectScalar(type);
|
||||
break;
|
||||
case "Model":
|
||||
projected = projectModel(type);
|
||||
break;
|
||||
|
@ -171,19 +174,15 @@ export function createProjector(
|
|||
}
|
||||
return alreadyProjected;
|
||||
}
|
||||
const childNamespaces = new Map<string, Namespace>();
|
||||
const childModels = new Map<string, Model>();
|
||||
const childOperations = new Map<string, Operation>();
|
||||
const childInterfaces = new Map<string, Interface>();
|
||||
const childUnions = new Map<string, Union>();
|
||||
const childEnums = new Map<string, Enum>();
|
||||
|
||||
const projectedNs = shallowClone(ns, {
|
||||
namespaces: childNamespaces,
|
||||
models: childModels,
|
||||
operations: childOperations,
|
||||
interfaces: childInterfaces,
|
||||
unions: childUnions,
|
||||
enums: childEnums,
|
||||
namespaces: new Map<string, Namespace>(),
|
||||
scalars: new Map<string, Scalar>(),
|
||||
models: new Map<string, Model>(),
|
||||
operations: new Map<string, Operation>(),
|
||||
interfaces: new Map<string, Interface>(),
|
||||
unions: new Map<string, Union>(),
|
||||
enums: new Map<string, Enum>(),
|
||||
decorators: [],
|
||||
});
|
||||
|
||||
|
@ -225,6 +224,13 @@ export function createProjector(
|
|||
}
|
||||
}
|
||||
|
||||
for (const scalar of ns.scalars.values()) {
|
||||
const projected = projectType(scalar);
|
||||
if (projected.kind === "Scalar") {
|
||||
projectedNs.scalars.set(projected.name, projected);
|
||||
}
|
||||
}
|
||||
|
||||
for (const childOperation of ns.operations.values()) {
|
||||
const projected = projectType(childOperation);
|
||||
if (projected.kind === "Operation") {
|
||||
|
@ -275,14 +281,10 @@ export function createProjector(
|
|||
}
|
||||
|
||||
if (model.indexer) {
|
||||
if (isNeverIndexer(model.indexer)) {
|
||||
projectedModel.indexer = { key: neverType, value: undefined };
|
||||
} else {
|
||||
projectedModel.indexer = {
|
||||
key: projectModel(model.indexer.key) as Model,
|
||||
value: projectType(model.indexer.value),
|
||||
};
|
||||
}
|
||||
projectedModel.indexer = {
|
||||
key: projectType(model.indexer.key) as Scalar,
|
||||
value: projectType(model.indexer.value),
|
||||
};
|
||||
}
|
||||
|
||||
projectedTypes.set(model, projectedModel);
|
||||
|
@ -311,6 +313,39 @@ export function createProjector(
|
|||
return projectedResult;
|
||||
}
|
||||
|
||||
function projectScalar(scalar: Scalar): Type {
|
||||
const projectedScalar = shallowClone(scalar, {
|
||||
derivedScalars: [],
|
||||
});
|
||||
|
||||
let templateArguments: Type[] | undefined;
|
||||
if (scalar.templateArguments !== undefined) {
|
||||
templateArguments = scalar.templateArguments.map(projectType);
|
||||
}
|
||||
projectedScalar.templateArguments = templateArguments;
|
||||
|
||||
if (scalar.baseScalar) {
|
||||
projectedScalar.baseScalar = projectType(scalar.baseScalar) as Scalar;
|
||||
}
|
||||
|
||||
projectedTypes.set(scalar, projectedScalar);
|
||||
|
||||
projectedScalar.decorators = projectDecorators(scalar.decorators);
|
||||
if (shouldFinishType(scalar)) {
|
||||
finishTypeForProgram(projectedProgram, projectedScalar);
|
||||
}
|
||||
const projectedResult = applyProjection(scalar, projectedScalar);
|
||||
if (
|
||||
!isNeverType(projectedResult) &&
|
||||
projectedResult.kind === "Scalar" &&
|
||||
projectedResult.baseScalar
|
||||
) {
|
||||
projectedResult.baseScalar.derivedScalars ??= [];
|
||||
projectedResult.baseScalar.derivedScalars.push(projectedScalar);
|
||||
}
|
||||
return projectedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should finish a type. The only time we don't finish is when it's
|
||||
* a template type, because we don't want to run decorators for templates.
|
||||
|
|
|
@ -104,6 +104,7 @@ export enum Token {
|
|||
|
||||
ImportKeyword = __StartStatementKeyword,
|
||||
ModelKeyword,
|
||||
ScalarKeyword,
|
||||
NamespaceKeyword,
|
||||
UsingKeyword,
|
||||
OpKeyword,
|
||||
|
@ -213,6 +214,7 @@ export const TokenDisplay = getTokenDisplayTable([
|
|||
[Token.Identifier, "identifier"],
|
||||
[Token.ImportKeyword, "'import'"],
|
||||
[Token.ModelKeyword, "'model'"],
|
||||
[Token.ScalarKeyword, "'scalar'"],
|
||||
[Token.NamespaceKeyword, "'namespace'"],
|
||||
[Token.UsingKeyword, "'using'"],
|
||||
[Token.OpKeyword, "'op'"],
|
||||
|
@ -240,6 +242,7 @@ export const TokenDisplay = getTokenDisplayTable([
|
|||
export const Keywords: ReadonlyMap<string, Token> = new Map([
|
||||
["import", Token.ImportKeyword],
|
||||
["model", Token.ModelKeyword],
|
||||
["scalar", Token.ScalarKeyword],
|
||||
["namespace", Token.NamespaceKeyword],
|
||||
["interface", Token.InterfaceKeyword],
|
||||
["union", Token.UnionKeyword],
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
ModelProperty,
|
||||
Namespace,
|
||||
Operation,
|
||||
Scalar,
|
||||
SemanticNodeListener,
|
||||
TemplateParameter,
|
||||
Tuple,
|
||||
|
@ -163,6 +164,9 @@ function navigateNamespaceType(namespace: Namespace, context: NavigationContext)
|
|||
for (const model of namespace.models.values()) {
|
||||
navigateModelType(model, context);
|
||||
}
|
||||
for (const scalar of namespace.scalars.values()) {
|
||||
navigateScalarType(scalar, context);
|
||||
}
|
||||
for (const operation of namespace.operations.values()) {
|
||||
navigateOperationType(operation, context);
|
||||
}
|
||||
|
@ -234,6 +238,18 @@ function navigateModelTypeProperty(property: ModelProperty, context: NavigationC
|
|||
navigateTypeInternal(property.type, context);
|
||||
}
|
||||
|
||||
function navigateScalarType(scalar: Scalar, context: NavigationContext) {
|
||||
if (checkVisited(context.visited, scalar)) {
|
||||
return;
|
||||
}
|
||||
if (context.emit("scalar", scalar) === ListenerFlow.NoRecursion) return;
|
||||
if (scalar.baseScalar) {
|
||||
navigateScalarType(scalar.baseScalar, context);
|
||||
}
|
||||
|
||||
context.emit("exitScalar", scalar);
|
||||
}
|
||||
|
||||
function navigateInterfaceType(type: Interface, context: NavigationContext) {
|
||||
if (checkVisited(context.visited, type)) {
|
||||
return;
|
||||
|
@ -298,6 +314,8 @@ function navigateTypeInternal(type: Type, context: NavigationContext) {
|
|||
switch (type.kind) {
|
||||
case "Model":
|
||||
return navigateModelType(type, context);
|
||||
case "Scalar":
|
||||
return navigateScalarType(type, context);
|
||||
case "ModelProperty":
|
||||
return navigateModelTypeProperty(type, context);
|
||||
case "Namespace":
|
||||
|
@ -372,6 +390,8 @@ const eventNames: Array<keyof SemanticNodeListener> = [
|
|||
"root",
|
||||
"templateParameter",
|
||||
"exitTemplateParameter",
|
||||
"scalar",
|
||||
"exitScalar",
|
||||
"model",
|
||||
"exitModel",
|
||||
"modelProperty",
|
||||
|
|
|
@ -1,17 +1,42 @@
|
|||
import { Program } from "./program.js";
|
||||
import {
|
||||
Enum,
|
||||
ErrorType,
|
||||
Interface,
|
||||
Model,
|
||||
Namespace,
|
||||
NeverType,
|
||||
Node,
|
||||
NullType,
|
||||
Operation,
|
||||
SyntaxKind,
|
||||
TemplateDeclarationNode,
|
||||
TemplatedType,
|
||||
Type,
|
||||
UnknownType,
|
||||
VoidType,
|
||||
} from "./types.js";
|
||||
|
||||
export function isErrorType(type: Type): type is ErrorType {
|
||||
return type.kind === "Intrinsic" && type.name === "ErrorType";
|
||||
}
|
||||
|
||||
export function isVoidType(type: Type): type is VoidType {
|
||||
return type.kind === "Intrinsic" && type.name === "void";
|
||||
}
|
||||
|
||||
export function isNeverType(type: Type): type is NeverType {
|
||||
return type.kind === "Intrinsic" && type.name === "never";
|
||||
}
|
||||
|
||||
export function isUnknownType(type: Type): type is UnknownType {
|
||||
return type.kind === "Intrinsic" && type.name === "unknown";
|
||||
}
|
||||
|
||||
export function isNullType(type: Type): type is NullType {
|
||||
return type.kind === "Intrinsic" && type.name === "null";
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup and find the node
|
||||
* @param node Node
|
||||
|
@ -20,6 +45,7 @@ import {
|
|||
export function getParentTemplateNode(node: Node): (Node & TemplateDeclarationNode) | undefined {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ModelStatement:
|
||||
case SyntaxKind.ScalarStatement:
|
||||
case SyntaxKind.OperationStatement:
|
||||
case SyntaxKind.InterfaceStatement:
|
||||
return node.templateParameters.length > 0 ? node : undefined;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { JSONSchemaType as AjvJSONSchemaType } from "ajv";
|
||||
import { Program } from "./program";
|
||||
import { Program } from "./program.js";
|
||||
|
||||
// prettier-ignore
|
||||
export type MarshalledValue<Type> =
|
||||
|
@ -58,6 +58,7 @@ export interface TemplatedTypeBase {
|
|||
export type Type =
|
||||
| Model
|
||||
| ModelProperty
|
||||
| Scalar
|
||||
| Interface
|
||||
| Enum
|
||||
| EnumMember
|
||||
|
@ -154,7 +155,7 @@ export interface Projector {
|
|||
|
||||
export interface IntrinsicType extends BaseType {
|
||||
kind: "Intrinsic";
|
||||
name: "ErrorType" | "void" | "never" | "unknown";
|
||||
name: "ErrorType" | "void" | "never" | "unknown" | "null";
|
||||
}
|
||||
|
||||
export interface ErrorType extends IntrinsicType {
|
||||
|
@ -172,6 +173,9 @@ export interface NeverType extends IntrinsicType {
|
|||
export interface UnknownType extends IntrinsicType {
|
||||
name: "unknown";
|
||||
}
|
||||
export interface NullType extends IntrinsicType {
|
||||
name: "null";
|
||||
}
|
||||
|
||||
// represents a type that is being returned from the
|
||||
// currently executing lambda or projection
|
||||
|
@ -180,7 +184,7 @@ export interface ReturnRecord {
|
|||
value: Type;
|
||||
}
|
||||
|
||||
export type IntrinsicModelName =
|
||||
export type IntrinsicScalarName =
|
||||
| "bytes"
|
||||
| "numeric"
|
||||
| "integer"
|
||||
|
@ -202,30 +206,25 @@ export type IntrinsicModelName =
|
|||
| "zonedDateTime"
|
||||
| "duration"
|
||||
| "boolean"
|
||||
| "null";
|
||||
|
||||
export type IntrinsicModel<T extends IntrinsicModelName = IntrinsicModelName> = Model & {
|
||||
name: T;
|
||||
};
|
||||
| "uri";
|
||||
|
||||
export type NeverIndexer = { key: NeverType; value: undefined };
|
||||
export type ModelKeyIndexer = {
|
||||
key: Model;
|
||||
export type ModelIndexer = {
|
||||
key: Scalar;
|
||||
value: Type;
|
||||
};
|
||||
export type ModelIndexer = ModelKeyIndexer | NeverIndexer;
|
||||
|
||||
export interface ArrayModelType extends Model {
|
||||
indexer: { key: Model; value: Type };
|
||||
indexer: { key: Scalar; value: Type };
|
||||
}
|
||||
|
||||
export interface RecordModelType extends Model {
|
||||
indexer: { key: Model; value: Type };
|
||||
indexer: { key: Scalar; value: Type };
|
||||
}
|
||||
|
||||
export interface Model extends BaseType, DecoratedType, TemplatedTypeBase {
|
||||
kind: "Model";
|
||||
name: IntrinsicModelName | string;
|
||||
name: string;
|
||||
node?:
|
||||
| ModelStatementNode
|
||||
| ModelExpressionNode
|
||||
|
@ -269,6 +268,32 @@ export interface ModelProperty extends BaseType, DecoratedType {
|
|||
model?: Model;
|
||||
}
|
||||
|
||||
export interface Scalar extends BaseType, DecoratedType, TemplatedTypeBase {
|
||||
kind: "Scalar";
|
||||
name: string;
|
||||
node: ScalarStatementNode;
|
||||
/**
|
||||
* Namespace the scalar was defined in.
|
||||
*/
|
||||
namespace?: Namespace;
|
||||
|
||||
/**
|
||||
* Scalar this scalar extends.
|
||||
*/
|
||||
baseScalar?: Scalar;
|
||||
|
||||
/**
|
||||
* Direct children. This is the reverse relation of @see baseScalar
|
||||
*/
|
||||
derivedScalars: Scalar[];
|
||||
|
||||
/**
|
||||
* Late-bound symbol of this model type.
|
||||
* @internal
|
||||
*/
|
||||
symbol?: Sym;
|
||||
}
|
||||
|
||||
export interface Interface extends BaseType, DecoratedType, TemplatedTypeBase {
|
||||
kind: "Interface";
|
||||
name: string;
|
||||
|
@ -319,6 +344,7 @@ export interface Namespace extends BaseType, DecoratedType {
|
|||
namespace?: Namespace;
|
||||
node: NamespaceStatementNode;
|
||||
models: Map<string, Model>;
|
||||
scalars: Map<string, Scalar>;
|
||||
operations: Map<string, Operation>;
|
||||
namespaces: Map<string, Namespace>;
|
||||
interfaces: Map<string, Interface>;
|
||||
|
@ -492,32 +518,33 @@ export const enum SymbolFlags {
|
|||
None = 0,
|
||||
Model = 1 << 1,
|
||||
ModelProperty = 1 << 2,
|
||||
Operation = 1 << 3,
|
||||
Enum = 1 << 4,
|
||||
EnumMember = 1 << 5,
|
||||
Interface = 1 << 6,
|
||||
InterfaceMember = 1 << 7,
|
||||
Union = 1 << 8,
|
||||
UnionVariant = 1 << 9,
|
||||
Alias = 1 << 10,
|
||||
Namespace = 1 << 11,
|
||||
Projection = 1 << 12,
|
||||
Decorator = 1 << 13,
|
||||
TemplateParameter = 1 << 14,
|
||||
ProjectionParameter = 1 << 15,
|
||||
Function = 1 << 16,
|
||||
FunctionParameter = 1 << 17,
|
||||
Using = 1 << 18,
|
||||
DuplicateUsing = 1 << 19,
|
||||
SourceFile = 1 << 20,
|
||||
Declaration = 1 << 21,
|
||||
Implementation = 1 << 22,
|
||||
Scalar = 1 << 3,
|
||||
Operation = 1 << 4,
|
||||
Enum = 1 << 5,
|
||||
EnumMember = 1 << 6,
|
||||
Interface = 1 << 7,
|
||||
InterfaceMember = 1 << 8,
|
||||
Union = 1 << 9,
|
||||
UnionVariant = 1 << 10,
|
||||
Alias = 1 << 11,
|
||||
Namespace = 1 << 12,
|
||||
Projection = 1 << 13,
|
||||
Decorator = 1 << 14,
|
||||
TemplateParameter = 1 << 15,
|
||||
ProjectionParameter = 1 << 16,
|
||||
Function = 1 << 17,
|
||||
FunctionParameter = 1 << 18,
|
||||
Using = 1 << 19,
|
||||
DuplicateUsing = 1 << 20,
|
||||
SourceFile = 1 << 21,
|
||||
Declaration = 1 << 22,
|
||||
Implementation = 1 << 23,
|
||||
|
||||
/**
|
||||
* A symbol which was late-bound, in which case, the type referred to
|
||||
* by this symbol is stored directly in the symbol.
|
||||
*/
|
||||
LateBound = 1 << 23,
|
||||
LateBound = 1 << 24,
|
||||
|
||||
ExportContainer = Namespace | SourceFile,
|
||||
/**
|
||||
|
@ -555,6 +582,7 @@ export enum SyntaxKind {
|
|||
ModelExpression,
|
||||
ModelProperty,
|
||||
ModelSpreadProperty,
|
||||
ScalarStatement,
|
||||
InterfaceStatement,
|
||||
UnionStatement,
|
||||
UnionVariant,
|
||||
|
@ -708,6 +736,7 @@ export type Node =
|
|||
*/
|
||||
export type TemplateableNode =
|
||||
| ModelStatementNode
|
||||
| ScalarStatementNode
|
||||
| AliasStatementNode
|
||||
| InterfaceStatementNode
|
||||
| OperationStatementNode
|
||||
|
@ -746,6 +775,7 @@ export interface CadlScriptNode extends DeclarationNode, BaseNode {
|
|||
export type Statement =
|
||||
| ImportStatementNode
|
||||
| ModelStatementNode
|
||||
| ScalarStatementNode
|
||||
| NamespaceStatementNode
|
||||
| InterfaceStatementNode
|
||||
| UnionStatementNode
|
||||
|
@ -766,6 +796,7 @@ export interface DeclarationNode {
|
|||
|
||||
export type Declaration =
|
||||
| ModelStatementNode
|
||||
| ScalarStatementNode
|
||||
| InterfaceStatementNode
|
||||
| UnionStatementNode
|
||||
| NamespaceStatementNode
|
||||
|
@ -913,6 +944,12 @@ export interface ModelStatementNode extends BaseNode, DeclarationNode, TemplateD
|
|||
readonly decorators: readonly DecoratorExpressionNode[];
|
||||
}
|
||||
|
||||
export interface ScalarStatementNode extends BaseNode, DeclarationNode, TemplateDeclarationNode {
|
||||
readonly kind: SyntaxKind.ScalarStatement;
|
||||
readonly extends?: TypeReferenceNode;
|
||||
readonly decorators: readonly DecoratorExpressionNode[];
|
||||
}
|
||||
|
||||
export interface InterfaceStatementNode extends BaseNode, DeclarationNode, TemplateDeclarationNode {
|
||||
readonly kind: SyntaxKind.InterfaceStatement;
|
||||
readonly operations: readonly OperationStatementNode[];
|
||||
|
|
|
@ -52,6 +52,7 @@ import {
|
|||
ProjectionTupleExpressionNode,
|
||||
ProjectionUnaryExpressionNode,
|
||||
ReturnExpressionNode,
|
||||
ScalarStatementNode,
|
||||
Statement,
|
||||
StringLiteralNode,
|
||||
SyntaxKind,
|
||||
|
@ -143,6 +144,8 @@ export function printNode(
|
|||
return printNamespaceStatement(path as AstPath<NamespaceStatementNode>, options, print);
|
||||
case SyntaxKind.ModelStatement:
|
||||
return printModelStatement(path as AstPath<ModelStatementNode>, options, print);
|
||||
case SyntaxKind.ScalarStatement:
|
||||
return printScalarStatement(path as AstPath<ScalarStatementNode>, options, print);
|
||||
case SyntaxKind.AliasStatement:
|
||||
return printAliasStatement(path as AstPath<AliasStatementNode>, options, print);
|
||||
case SyntaxKind.EnumStatement:
|
||||
|
@ -1030,6 +1033,28 @@ function isModelExpressionInBlock(
|
|||
}
|
||||
}
|
||||
|
||||
export function printScalarStatement(
|
||||
path: AstPath<ScalarStatementNode>,
|
||||
options: CadlPrettierOptions,
|
||||
print: PrettierChildPrint
|
||||
) {
|
||||
const node = path.getValue();
|
||||
const id = path.call(print, "id");
|
||||
const template = printTemplateParameters(path, options, print, "templateParameters");
|
||||
|
||||
const heritage = node.extends
|
||||
? [ifBreak(line, " "), "extends ", path.call(print, "extends")]
|
||||
: "";
|
||||
return [
|
||||
printDecorators(path, options, print, { tryInline: false }).decorators,
|
||||
"scalar ",
|
||||
id,
|
||||
template,
|
||||
group(indent(["", heritage])),
|
||||
";",
|
||||
];
|
||||
}
|
||||
|
||||
export function printNamespaceStatement(
|
||||
path: AstPath<NamespaceStatementNode>,
|
||||
options: CadlPrettierOptions,
|
||||
|
|
|
@ -16,23 +16,22 @@ extern dec service(target: unknown, options?: ServiceOptions);
|
|||
|
||||
extern dec error(target: object);
|
||||
|
||||
// TODO replace unknown with `string` when @intrinsic is gone https://github.com/microsoft/cadl/issues/1192
|
||||
extern dec format(target: unknown | ModelProperty, format: string);
|
||||
extern dec pattern(target: unknown | ModelProperty, pattern: string);
|
||||
extern dec format(target: string | bytes | ModelProperty, format: string);
|
||||
extern dec pattern(target: string | bytes | ModelProperty, pattern: string);
|
||||
|
||||
extern dec minLength(target: unknown | ModelProperty, value: integer);
|
||||
extern dec maxLength(target: unknown | ModelProperty, value: integer);
|
||||
extern dec minLength(target: string | ModelProperty, value: integer);
|
||||
extern dec maxLength(target: string | ModelProperty, value: integer);
|
||||
|
||||
extern dec minItems(target: unknown[] | ModelProperty, value: integer);
|
||||
extern dec maxItems(target: unknown[] | ModelProperty, value: integer);
|
||||
|
||||
extern dec minValue(target: unknown | ModelProperty, value: numeric);
|
||||
extern dec maxValue(target: unknown | ModelProperty, value: numeric);
|
||||
extern dec minValue(target: numeric | ModelProperty, value: numeric);
|
||||
extern dec maxValue(target: numeric | ModelProperty, value: numeric);
|
||||
|
||||
extern dec secret(target: unknown | ModelProperty);
|
||||
extern dec secret(target: string | ModelProperty);
|
||||
extern dec tag(target: Namespace | Interface | Operation, tag: string);
|
||||
extern dec friendlyName(target: unknown, name: string, formatArgs?: unknown);
|
||||
extern dec knownValues(target: unknown | ModelProperty, values: Enum);
|
||||
extern dec knownValues(target: string | numeric | ModelProperty, values: Enum);
|
||||
extern dec key(target: ModelProperty, altName?: string);
|
||||
extern dec overload(target: Operation, overloadbase: Operation);
|
||||
extern dec projectedName(target: unknown, targetName: string, projectedName: string);
|
||||
|
|
|
@ -2,26 +2,23 @@ import {
|
|||
validateDecoratorTarget,
|
||||
validateDecoratorTargetIntrinsic,
|
||||
} from "../core/decorator-utils.js";
|
||||
import { getDiscriminatedUnion } from "../core/index.js";
|
||||
import { getDiscriminatedUnion } from "../core/helpers/index.js";
|
||||
import { createDiagnostic, reportDiagnostic } from "../core/messages.js";
|
||||
import { Program } from "../core/program.js";
|
||||
import { Program, ProjectedProgram } from "../core/program.js";
|
||||
import {
|
||||
ArrayModelType,
|
||||
DecoratorContext,
|
||||
Enum,
|
||||
EnumMember,
|
||||
Interface,
|
||||
IntrinsicModelName,
|
||||
Model,
|
||||
ModelIndexer,
|
||||
ModelProperty,
|
||||
Namespace,
|
||||
NeverType,
|
||||
Operation,
|
||||
Scalar,
|
||||
Type,
|
||||
Union,
|
||||
UnknownType,
|
||||
VoidType,
|
||||
} from "../core/types.js";
|
||||
export * from "./service.js";
|
||||
|
||||
|
@ -111,20 +108,8 @@ export function $inspectTypeName(program: Program, target: Type, text: string) {
|
|||
console.log(program.checker.getTypeName(target));
|
||||
}
|
||||
|
||||
const intrinsicsKey = createStateSymbol("intrinsics");
|
||||
export function $intrinsic(context: DecoratorContext, target: Type, name: IntrinsicModelName) {
|
||||
context.program.stateMap(intrinsicsKey).set(target, name);
|
||||
}
|
||||
|
||||
export function isIntrinsic(program: Program, target: Type | undefined): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
return program.stateMap(intrinsicsKey).has(target);
|
||||
}
|
||||
|
||||
const indexTypeKey = createStateSymbol("index");
|
||||
export function $indexer(context: DecoratorContext, target: Type, key: Model, value: Type) {
|
||||
export function $indexer(context: DecoratorContext, target: Type, key: Scalar, value: Type) {
|
||||
const indexer: ModelIndexer = { key, value };
|
||||
context.program.stateMap(indexTypeKey).set(target, indexer);
|
||||
}
|
||||
|
@ -133,35 +118,20 @@ export function getIndexer(program: Program, target: Type): ModelIndexer | undef
|
|||
return program.stateMap(indexTypeKey).get(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* The top level name of the intrinsic model.
|
||||
*
|
||||
* string => "string"
|
||||
* model CustomString is string => "string"
|
||||
*/
|
||||
export function getIntrinsicModelName(program: Program, target: Type): IntrinsicModelName {
|
||||
return program.stateMap(intrinsicsKey).get(target);
|
||||
export function isStringType(program: Program | ProjectedProgram, target: Type): target is Scalar {
|
||||
const coreType = program.checker.getStdType("string");
|
||||
const stringType = target.projector ? target.projector.projectType(coreType) : coreType;
|
||||
return (
|
||||
target.kind === "Scalar" && program.checker.isTypeAssignableTo(target, stringType, target)[0]
|
||||
);
|
||||
}
|
||||
|
||||
export function isStringType(program: Program, target: Type): boolean {
|
||||
const intrinsicType = getIntrinsicModelName(program, target);
|
||||
return intrinsicType !== undefined && intrinsicType === "string";
|
||||
}
|
||||
|
||||
export function isErrorType(type: Type): boolean {
|
||||
return type.kind === "Intrinsic" && type.name === "ErrorType";
|
||||
}
|
||||
|
||||
export function isVoidType(type: Type): type is VoidType {
|
||||
return type.kind === "Intrinsic" && type.name === "void";
|
||||
}
|
||||
|
||||
export function isNeverType(type: Type): type is NeverType {
|
||||
return type.kind === "Intrinsic" && type.name === "never";
|
||||
}
|
||||
|
||||
export function isUnknownType(type: Type): type is UnknownType {
|
||||
return type.kind === "Intrinsic" && type.name === "unknown";
|
||||
export function isNumericType(program: Program | ProjectedProgram, target: Type): target is Scalar {
|
||||
const coreType = program.checker.getStdType("numeric");
|
||||
const numericType = target.projector ? target.projector.projectType(coreType) : coreType;
|
||||
return (
|
||||
target.kind === "Scalar" && program.checker.isTypeAssignableTo(target, numericType, target)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,7 +139,7 @@ export function isUnknownType(type: Type): type is UnknownType {
|
|||
* @param type Model type
|
||||
*/
|
||||
export function isArrayModelType(program: Program, type: Model): type is ArrayModelType {
|
||||
return Boolean(type.indexer && getIntrinsicModelName(program, type.indexer.key) === "integer");
|
||||
return Boolean(type.indexer && type.indexer.key.name === "integer");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,32 +147,13 @@ export function isArrayModelType(program: Program, type: Model): type is ArrayMo
|
|||
* @param type Model type
|
||||
*/
|
||||
export function isRecordModelType(program: Program, type: Model): type is ArrayModelType {
|
||||
return Boolean(type.indexer && getIntrinsicModelName(program, type.indexer.key) === "string");
|
||||
}
|
||||
|
||||
const numericTypesKey = createStateSymbol("numeric");
|
||||
export function $numeric(context: DecoratorContext, target: Type) {
|
||||
const { program } = context;
|
||||
if (!isIntrinsic(program, target)) {
|
||||
program.reportDiagnostic(
|
||||
createDiagnostic({
|
||||
code: "decorator-wrong-target",
|
||||
format: { decorator: "@numeric", to: "non-intrinsic type" },
|
||||
target,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!validateDecoratorTarget(context, target, "@numeric", "Model")) {
|
||||
return;
|
||||
}
|
||||
program.stateSet(numericTypesKey).add(target);
|
||||
return Boolean(type.indexer && type.indexer.key.name === "string");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the property or the model itself.
|
||||
*/
|
||||
export function getPropertyType(target: Model | ModelProperty): Type {
|
||||
export function getPropertyType(target: Scalar | ModelProperty): Type {
|
||||
if (target.kind === "ModelProperty") {
|
||||
return target.type;
|
||||
} else {
|
||||
|
@ -210,10 +161,6 @@ export function getPropertyType(target: Model | ModelProperty): Type {
|
|||
}
|
||||
}
|
||||
|
||||
export function isNumericType(program: Program, target: Type): boolean {
|
||||
return isIntrinsic(program, target) && program.stateSet(numericTypesKey).has(target);
|
||||
}
|
||||
|
||||
// -- @error decorator ----------------------
|
||||
|
||||
const errorKey = createStateSymbol("error");
|
||||
|
@ -248,7 +195,7 @@ const formatValuesKey = createStateSymbol("formatValues");
|
|||
*
|
||||
* `@format` can be specified on a type that extends from `string` or a `string`-typed model property.
|
||||
*/
|
||||
export function $format(context: DecoratorContext, target: Model | ModelProperty, format: string) {
|
||||
export function $format(context: DecoratorContext, target: Scalar | ModelProperty, format: string) {
|
||||
if (!validateDecoratorTargetIntrinsic(context, target, "@format", ["string", "bytes"])) {
|
||||
return;
|
||||
}
|
||||
|
@ -266,7 +213,7 @@ const patternValuesKey = createStateSymbol("patternValues");
|
|||
|
||||
export function $pattern(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
pattern: string
|
||||
) {
|
||||
if (!validateDecoratorTargetIntrinsic(context, target, "@pattern", "string")) {
|
||||
|
@ -286,7 +233,7 @@ const minLengthValuesKey = createStateSymbol("minLengthValues");
|
|||
|
||||
export function $minLength(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
minLength: number
|
||||
) {
|
||||
if (
|
||||
|
@ -309,7 +256,7 @@ const maxLengthValuesKey = createStateSymbol("maxLengthValues");
|
|||
|
||||
export function $maxLength(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
maxLength: number
|
||||
) {
|
||||
if (
|
||||
|
@ -393,7 +340,7 @@ const minValuesKey = createStateSymbol("minValues");
|
|||
|
||||
export function $minValue(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
minValue: number
|
||||
) {
|
||||
const { program } = context;
|
||||
|
@ -425,7 +372,7 @@ const maxValuesKey = createStateSymbol("maxValues");
|
|||
|
||||
export function $maxValue(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
maxValue: number
|
||||
) {
|
||||
const { program } = context;
|
||||
|
@ -459,7 +406,7 @@ const secretTypesKey = createStateSymbol("secretTypes");
|
|||
* @param context Decorator context
|
||||
* @param target Decorator target, either a string model or a property with type string.
|
||||
*/
|
||||
export function $secret(context: DecoratorContext, target: Model | ModelProperty) {
|
||||
export function $secret(context: DecoratorContext, target: Scalar | ModelProperty) {
|
||||
if (!validateDecoratorTargetIntrinsic(context, target, "@secret", "string")) {
|
||||
return;
|
||||
}
|
||||
|
@ -672,7 +619,7 @@ const knownValuesKey = createStateSymbol("knownValues");
|
|||
*/
|
||||
export function $knownValues(
|
||||
context: DecoratorContext,
|
||||
target: Model | ModelProperty,
|
||||
target: Scalar | ModelProperty,
|
||||
knownValues: Enum
|
||||
) {
|
||||
if (
|
||||
|
@ -690,13 +637,13 @@ export function $knownValues(
|
|||
}
|
||||
|
||||
for (const member of knownValues.members.values()) {
|
||||
const intrinsicType = getIntrinsicModelName(context.program, getPropertyType(target));
|
||||
if (!isEnumMemberAssignableToType(intrinsicType, member)) {
|
||||
const propertyType = getPropertyType(target);
|
||||
if (!isEnumMemberAssignableToType(context.program, propertyType, member)) {
|
||||
reportDiagnostic(context.program, {
|
||||
code: "known-values-invalid-enum",
|
||||
format: {
|
||||
member: member.name,
|
||||
type: intrinsicType,
|
||||
type: context.program.checker.getTypeName(propertyType),
|
||||
},
|
||||
target,
|
||||
});
|
||||
|
@ -706,29 +653,19 @@ export function $knownValues(
|
|||
context.program.stateMap(knownValuesKey).set(target, knownValues);
|
||||
}
|
||||
|
||||
function isEnumMemberAssignableToType(typeName: IntrinsicModelName, member: EnumMember) {
|
||||
function isEnumMemberAssignableToType(program: Program, typeName: Type, member: EnumMember) {
|
||||
const memberType = member.value !== undefined ? typeof member.value : "string";
|
||||
switch (memberType) {
|
||||
case "string":
|
||||
return typeName === "string";
|
||||
return isStringType(program, typeName);
|
||||
case "number":
|
||||
switch (typeName) {
|
||||
case "int8":
|
||||
case "int16":
|
||||
case "int32":
|
||||
case "int64":
|
||||
case "float32":
|
||||
case "float64":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return isNumericType(program, typeName);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getKnownValues(program: Program, target: Model | ModelProperty): Enum | undefined {
|
||||
export function getKnownValues(program: Program, target: Scalar | ModelProperty): Enum | undefined {
|
||||
return program.stateMap(knownValuesKey).get(target);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
namespace Cadl;
|
||||
|
||||
scalar bytes;
|
||||
scalar numeric;
|
||||
scalar integer extends numeric;
|
||||
scalar float extends numeric;
|
||||
scalar int64 extends integer;
|
||||
scalar int32 extends int64;
|
||||
scalar int16 extends int32;
|
||||
scalar int8 extends int16;
|
||||
scalar uint64 extends integer;
|
||||
scalar uint32 extends uint64;
|
||||
scalar uint16 extends uint32;
|
||||
scalar uint8 extends uint16;
|
||||
scalar safeint extends int64;
|
||||
scalar float64 extends float;
|
||||
scalar float32 extends float64;
|
||||
scalar string;
|
||||
scalar plainDate;
|
||||
scalar plainTime;
|
||||
scalar zonedDateTime;
|
||||
scalar duration;
|
||||
scalar boolean;
|
||||
|
||||
model object {}
|
||||
|
||||
@indexer(integer, T)
|
||||
|
@ -8,91 +30,11 @@ model Array<T> {}
|
|||
@indexer(string, T)
|
||||
model Record<T> {}
|
||||
|
||||
@intrinsic("bytes")
|
||||
model bytes {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("numeric")
|
||||
model numeric {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("integer")
|
||||
model integer {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("float")
|
||||
model float {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("int64")
|
||||
model int64 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("int32")
|
||||
model int32 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("int16")
|
||||
model int16 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("int8")
|
||||
model int8 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("uint64")
|
||||
model uint64 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("uint32")
|
||||
model uint32 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("uint16")
|
||||
model uint16 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("uint8")
|
||||
model uint8 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("safeint")
|
||||
model safeint {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("float32")
|
||||
model float32 {}
|
||||
|
||||
@numeric
|
||||
@intrinsic("float64")
|
||||
model float64 {}
|
||||
|
||||
@intrinsic("string")
|
||||
model string {}
|
||||
|
||||
@intrinsic("plainDate")
|
||||
model plainDate {}
|
||||
|
||||
@intrinsic("plainTime")
|
||||
model plainTime {}
|
||||
|
||||
@intrinsic("zonedDateTime")
|
||||
model zonedDateTime {}
|
||||
|
||||
@intrinsic("duration")
|
||||
model duration {}
|
||||
|
||||
@intrinsic("boolean")
|
||||
model boolean {}
|
||||
|
||||
@intrinsic("null")
|
||||
model null {}
|
||||
|
||||
/**
|
||||
* Represent a URI string.
|
||||
*/
|
||||
@format("uri")
|
||||
model uri is string;
|
||||
scalar uri extends string;
|
||||
|
||||
@deprecated("Map is deprecated, use Record<T> instead")
|
||||
model Map<K, V> is Record<V>;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Set of projections consuming the @projectedName decorator
|
||||
|
||||
#suppress "projections-are-experimental"
|
||||
projection op#target {
|
||||
to(targetName) {
|
||||
|
|
|
@ -101,7 +101,7 @@ import {
|
|||
getSourceFileKindFromExt,
|
||||
loadFile,
|
||||
} from "../core/util.js";
|
||||
import { getDoc, isDeprecated, isIntrinsic } from "../lib/decorators.js";
|
||||
import { getDoc, isDeprecated } from "../lib/decorators.js";
|
||||
import { getSymbolStructure } from "./symbol-structure.js";
|
||||
import { getTypeSignature } from "./type-signature.js";
|
||||
|
||||
|
@ -221,6 +221,7 @@ const keywords = [
|
|||
// Root and namespace
|
||||
["using", { root: true, namespace: true }],
|
||||
["model", { root: true, namespace: true }],
|
||||
["scalar", { root: true, namespace: true }],
|
||||
["namespace", { root: true, namespace: true }],
|
||||
["interface", { root: true, namespace: true }],
|
||||
["union", { root: true, namespace: true }],
|
||||
|
@ -1023,7 +1024,9 @@ export function createServer(host: ServerHost): Server {
|
|||
case SyntaxKind.AliasStatement:
|
||||
return CompletionItemKind.Variable;
|
||||
case SyntaxKind.ModelStatement:
|
||||
return isIntrinsic(program, target) ? CompletionItemKind.Keyword : CompletionItemKind.Class;
|
||||
return CompletionItemKind.Class;
|
||||
case SyntaxKind.ScalarStatement:
|
||||
return CompletionItemKind.Unit;
|
||||
case SyntaxKind.ModelProperty:
|
||||
return CompletionItemKind.Field;
|
||||
case SyntaxKind.OperationStatement:
|
||||
|
@ -1106,6 +1109,9 @@ export function createServer(host: ServerHost): Server {
|
|||
case SyntaxKind.ModelStatement:
|
||||
classify(node.id, SemanticTokenKind.Struct);
|
||||
break;
|
||||
case SyntaxKind.ScalarStatement:
|
||||
classify(node.id, SemanticTokenKind.Type);
|
||||
break;
|
||||
case SyntaxKind.EnumStatement:
|
||||
classify(node.id, SemanticTokenKind.Enum);
|
||||
break;
|
||||
|
|
|
@ -390,6 +390,33 @@ const modelStatement: BeginEndRule = {
|
|||
],
|
||||
};
|
||||
|
||||
const scalarExtends: BeginEndRule = {
|
||||
key: "scalar-extends",
|
||||
scope: meta,
|
||||
begin: "\\b(extends)\\b",
|
||||
beginCaptures: {
|
||||
"1": { scope: "keyword.other.cadl" },
|
||||
},
|
||||
end: universalEndExceptComma,
|
||||
patterns: [expression, punctuationComma],
|
||||
};
|
||||
|
||||
const scalarStatement: BeginEndRule = {
|
||||
key: "scalar-statement",
|
||||
scope: meta,
|
||||
begin: "\\b(scalar)\\b",
|
||||
beginCaptures: {
|
||||
"1": { scope: "keyword.other.cadl" },
|
||||
},
|
||||
end: universalEnd,
|
||||
patterns: [
|
||||
token,
|
||||
typeParameters,
|
||||
scalarExtends, // before expression or `extends` will look like type name
|
||||
expression, // enough to match name, type parameters, and body.
|
||||
],
|
||||
};
|
||||
|
||||
const enumStatement: BeginEndRule = {
|
||||
key: "enum-statement",
|
||||
scope: meta,
|
||||
|
@ -762,6 +789,7 @@ statement.patterns = [
|
|||
augmentDecoratorStatement,
|
||||
decorator,
|
||||
modelStatement,
|
||||
scalarStatement,
|
||||
unionStatement,
|
||||
interfaceStatement,
|
||||
enumStatement,
|
||||
|
|
|
@ -29,8 +29,8 @@ describe("compiler: aliases", () => {
|
|||
const propType: Union = A.properties.get("prop")!.type as Union;
|
||||
strictEqual(propType.kind, "Union");
|
||||
strictEqual(propType.options.length, 4);
|
||||
strictEqual(propType.options[0].kind, "Model");
|
||||
strictEqual(propType.options[1].kind, "Model");
|
||||
strictEqual(propType.options[0].kind, "Scalar");
|
||||
strictEqual(propType.options[1].kind, "Scalar");
|
||||
strictEqual(propType.options[2].kind, "String");
|
||||
strictEqual(propType.options[3].kind, "Number");
|
||||
});
|
||||
|
@ -56,8 +56,8 @@ describe("compiler: aliases", () => {
|
|||
const propType: Union = A.properties.get("prop")!.type as Union;
|
||||
strictEqual(propType.kind, "Union");
|
||||
strictEqual(propType.options.length, 5);
|
||||
strictEqual(propType.options[0].kind, "Model");
|
||||
strictEqual(propType.options[1].kind, "Model");
|
||||
strictEqual(propType.options[0].kind, "Scalar");
|
||||
strictEqual(propType.options[1].kind, "Scalar");
|
||||
strictEqual(propType.options[2].kind, "String");
|
||||
strictEqual(propType.options[3].kind, "Number");
|
||||
strictEqual(propType.options[4].kind, "String");
|
||||
|
@ -82,7 +82,7 @@ describe("compiler: aliases", () => {
|
|||
const propType: Union = A.properties.get("prop")!.type as Union;
|
||||
strictEqual(propType.kind, "Union");
|
||||
strictEqual(propType.options.length, 2);
|
||||
strictEqual(propType.options[0].kind, "Model");
|
||||
strictEqual(propType.options[0].kind, "Scalar");
|
||||
strictEqual(propType.options[1].kind, "String");
|
||||
});
|
||||
|
||||
|
@ -106,9 +106,9 @@ describe("compiler: aliases", () => {
|
|||
const propType: Union = A.properties.get("prop")!.type as Union;
|
||||
strictEqual(propType.kind, "Union");
|
||||
strictEqual(propType.options.length, 4);
|
||||
strictEqual(propType.options[0].kind, "Model");
|
||||
strictEqual(propType.options[0].kind, "Scalar");
|
||||
strictEqual(propType.options[1].kind, "String");
|
||||
strictEqual(propType.options[2].kind, "Model");
|
||||
strictEqual(propType.options[2].kind, "Scalar");
|
||||
strictEqual(propType.options[3].kind, "Number");
|
||||
});
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ describe("compiler: effective type", () => {
|
|||
strictEqual(Derived.kind, "Model" as const);
|
||||
|
||||
const propType = Derived.properties.get("test")?.type;
|
||||
strictEqual(propType?.kind, "Model" as const);
|
||||
strictEqual(propType?.kind, "Scalar" as const);
|
||||
|
||||
const effective = getEffectiveModelType(testHost.program, Derived, removeFilter);
|
||||
expectIdenticalTypes(effective, Base);
|
||||
|
@ -227,7 +227,7 @@ describe("compiler: effective type", () => {
|
|||
strictEqual(Derived.kind, "Model" as const);
|
||||
|
||||
const propType = Derived.properties.get("test")?.type;
|
||||
strictEqual(propType?.kind, "Model" as const);
|
||||
strictEqual(propType?.kind, "Scalar" as const);
|
||||
|
||||
const effective = getEffectiveModelType(testHost.program, Derived, removeFilter);
|
||||
expectIdenticalTypes(effective, Base);
|
||||
|
@ -260,7 +260,7 @@ describe("compiler: effective type", () => {
|
|||
strictEqual(Derived.kind, "Model" as const);
|
||||
|
||||
const propType = Derived.properties.get("test")?.type;
|
||||
strictEqual(propType?.kind, "Model" as const);
|
||||
strictEqual(propType?.kind, "Scalar" as const);
|
||||
|
||||
const effective = getEffectiveModelType(testHost.program, Derived, removeFilter);
|
||||
expectIdenticalTypes(effective, Middle);
|
||||
|
|
|
@ -76,8 +76,8 @@ describe("compiler: interfaces", () => {
|
|||
};
|
||||
|
||||
strictEqual(Foo.operations.size, 1);
|
||||
const returnType: Model = Foo.operations.get("bar")!.returnType as Model;
|
||||
strictEqual(returnType.kind, "Model");
|
||||
const returnType = Foo.operations.get("bar")!.returnType;
|
||||
strictEqual(returnType.kind, "Scalar" as const);
|
||||
strictEqual(returnType.name, "int32");
|
||||
});
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { ok, strictEqual } from "assert";
|
||||
import { getIntrinsicModelName, isIntrinsic } from "../../lib/decorators.js";
|
||||
import {
|
||||
BasicTestRunner,
|
||||
createTestHost,
|
||||
createTestWrapper,
|
||||
expectDiagnostics,
|
||||
} from "../../testing/index.js";
|
||||
|
||||
describe("compiler: checker: intrinsic", () => {
|
||||
let runner: BasicTestRunner;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = createTestWrapper(await createTestHost(), (x) => x);
|
||||
});
|
||||
|
||||
["string", "int32", "int64", "float32", "float64"].forEach((x) => {
|
||||
it(`'model X extends ${x}' emit diagnostic`, async () => {
|
||||
const diagnostics = await runner.diagnose("model X extends string {}");
|
||||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "extend-primitive",
|
||||
message: "Cannot extend primitive types. Use 'model X is string' instead.",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("can use is with intrinsic types", async () => {
|
||||
const { CustomStr } = await runner.compile("@test model CustomStr is string {}");
|
||||
ok(CustomStr);
|
||||
|
||||
ok(isIntrinsic(runner.program, CustomStr));
|
||||
strictEqual(getIntrinsicModelName(runner.program, CustomStr), "string");
|
||||
});
|
||||
});
|
|
@ -440,7 +440,7 @@ describe("compiler: models", () => {
|
|||
|
||||
strictEqual(Pet.derivedModels[1].name, "TPet");
|
||||
ok(Pet.derivedModels[1].templateArguments);
|
||||
strictEqual(Pet.derivedModels[1].templateArguments[0].kind, "Model");
|
||||
strictEqual(Pet.derivedModels[1].templateArguments[0].kind, "Scalar");
|
||||
strictEqual((Pet.derivedModels[1].templateArguments[0] as Model).name, "string");
|
||||
|
||||
strictEqual(Pet.derivedModels[2], Cat);
|
||||
|
@ -499,10 +499,7 @@ describe("compiler: models", () => {
|
|||
);
|
||||
const diagnostics = await testHost.diagnose("main.cadl");
|
||||
strictEqual(diagnostics.length, 1);
|
||||
strictEqual(
|
||||
diagnostics[0].message,
|
||||
"Model type 'A' recursively references itself as a base type."
|
||||
);
|
||||
strictEqual(diagnostics[0].message, "Type 'A' recursively references itself as a base type.");
|
||||
});
|
||||
|
||||
it("emit error when extends circular reference", async () => {
|
||||
|
@ -515,10 +512,7 @@ describe("compiler: models", () => {
|
|||
);
|
||||
const diagnostics = await testHost.diagnose("main.cadl");
|
||||
strictEqual(diagnostics.length, 1);
|
||||
strictEqual(
|
||||
diagnostics[0].message,
|
||||
"Model type 'A' recursively references itself as a base type."
|
||||
);
|
||||
strictEqual(diagnostics[0].message, "Type 'A' recursively references itself as a base type.");
|
||||
});
|
||||
|
||||
it("emit no error when extends has property to base model", async () => {
|
||||
|
@ -685,10 +679,7 @@ describe("compiler: models", () => {
|
|||
);
|
||||
const diagnostics = await testHost.diagnose("main.cadl");
|
||||
strictEqual(diagnostics.length, 1);
|
||||
strictEqual(
|
||||
diagnostics[0].message,
|
||||
"Model type 'A' recursively references itself as a base type."
|
||||
);
|
||||
strictEqual(diagnostics[0].message, "Type 'A' recursively references itself as a base type.");
|
||||
});
|
||||
|
||||
it("emit single error when is itself as a templated with multiple instantiations", async () => {
|
||||
|
@ -707,7 +698,7 @@ describe("compiler: models", () => {
|
|||
expectDiagnostics(diagnostics, [
|
||||
{
|
||||
code: "circular-base-type",
|
||||
message: "Model type 'A' recursively references itself as a base type.",
|
||||
message: "Type 'A' recursively references itself as a base type.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -722,10 +713,7 @@ describe("compiler: models", () => {
|
|||
);
|
||||
const diagnostics = await testHost.diagnose("main.cadl");
|
||||
strictEqual(diagnostics.length, 1);
|
||||
strictEqual(
|
||||
diagnostics[0].message,
|
||||
"Model type 'A' recursively references itself as a base type."
|
||||
);
|
||||
strictEqual(diagnostics[0].message, "Type 'A' recursively references itself as a base type.");
|
||||
});
|
||||
|
||||
it("emit error when 'is' circular reference via extends", async () => {
|
||||
|
@ -738,10 +726,7 @@ describe("compiler: models", () => {
|
|||
);
|
||||
const diagnostics = await testHost.diagnose("main.cadl");
|
||||
strictEqual(diagnostics.length, 1);
|
||||
strictEqual(
|
||||
diagnostics[0].message,
|
||||
"Model type 'A' recursively references itself as a base type."
|
||||
);
|
||||
strictEqual(diagnostics[0].message, "Type 'A' recursively references itself as a base type.");
|
||||
});
|
||||
|
||||
it("emit no error when extends has property to base model", async () => {
|
||||
|
|
|
@ -39,9 +39,9 @@ describe("compiler: operations", () => {
|
|||
const props = Array.from(newFoo.parameters.properties.values());
|
||||
|
||||
strictEqual(props[0].name, "name");
|
||||
strictEqual(props[0].type.kind, "Model");
|
||||
strictEqual(props[0].type.kind, "Scalar");
|
||||
strictEqual(props[1].name, "payload");
|
||||
strictEqual(props[1].type.kind, "Model");
|
||||
strictEqual(props[1].type.kind, "Scalar");
|
||||
});
|
||||
|
||||
it("can be defined based on other operation references", async () => {
|
||||
|
@ -62,9 +62,9 @@ describe("compiler: operations", () => {
|
|||
const props = Array.from(newFoo.parameters.properties.values());
|
||||
|
||||
strictEqual(props[0].name, "name");
|
||||
strictEqual(props[0].type.kind, "Model");
|
||||
strictEqual(props[0].type.kind, "Scalar");
|
||||
strictEqual(props[1].name, "payload");
|
||||
strictEqual(props[1].type.kind, "Model");
|
||||
strictEqual(props[1].type.kind, "Scalar");
|
||||
});
|
||||
|
||||
it("can reference an operation when being defined in an interface", async () => {
|
||||
|
@ -83,9 +83,9 @@ describe("compiler: operations", () => {
|
|||
const props = Array.from(newFoo.parameters.properties.values());
|
||||
|
||||
strictEqual(props[0].name, "name");
|
||||
strictEqual(props[0].type.kind, "Model");
|
||||
strictEqual(props[0].type.kind, "Scalar");
|
||||
strictEqual(props[1].name, "payload");
|
||||
strictEqual(props[1].type.kind, "Model");
|
||||
strictEqual(props[1].type.kind, "Scalar");
|
||||
});
|
||||
|
||||
it("can reference an operation defined inside an interface", async () => {
|
||||
|
@ -102,7 +102,7 @@ describe("compiler: operations", () => {
|
|||
|
||||
const { newFoo } = (await testHost.compile("./main.cadl")) as { newFoo: Operation };
|
||||
|
||||
strictEqual(newFoo.returnType.kind, "Model" as const);
|
||||
strictEqual(newFoo.returnType.kind, "Scalar" as const);
|
||||
strictEqual(newFoo.returnType.name, "boolean");
|
||||
});
|
||||
|
||||
|
@ -119,7 +119,7 @@ describe("compiler: operations", () => {
|
|||
|
||||
const { newFoo } = (await testHost.compile("./main.cadl")) as { newFoo: Operation };
|
||||
|
||||
strictEqual(newFoo.returnType.kind, "Model" as const);
|
||||
strictEqual(newFoo.returnType.kind, "Scalar" as const);
|
||||
strictEqual(newFoo.returnType.name, "boolean");
|
||||
});
|
||||
|
||||
|
@ -187,7 +187,7 @@ describe("compiler: operations", () => {
|
|||
strictEqual(newFoo.parameters.properties.size, 2);
|
||||
|
||||
// Check that the decorators were applied correctly to `newFoo`
|
||||
strictEqual(alphaTargets.get(newFoo)?.kind, "Model");
|
||||
strictEqual(alphaTargets.get(newFoo)?.kind, "Scalar");
|
||||
ok(betaTargets.has(newFoo));
|
||||
ok(gammaTargets.has(newFoo));
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { deepStrictEqual, ok, strictEqual } from "assert";
|
||||
import { isNeverIndexer, Model } from "../../core/index.js";
|
||||
import { Model } from "../../core/index.js";
|
||||
import {
|
||||
BasicTestRunner,
|
||||
createTestHost,
|
||||
|
@ -59,17 +59,6 @@ describe("compiler: checker: type relations", () => {
|
|||
expectDiagnosticEmpty(diagnostics);
|
||||
});
|
||||
|
||||
it("cannot add property to primitive type", async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
model Foo is string {
|
||||
prop1: string;
|
||||
}`);
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "no-prop",
|
||||
message: "Property 'prop1' cannot be defined because model cannot hold properties.",
|
||||
});
|
||||
});
|
||||
|
||||
it("cannot add property incompatible with indexer", async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
model Foo is Record<int32> {
|
||||
|
@ -88,19 +77,18 @@ describe("compiler: checker: type relations", () => {
|
|||
`)) as { Bar: Model };
|
||||
const Foo = Bar.properties.get("foo")!.type as Model;
|
||||
ok(Foo.indexer);
|
||||
ok(!isNeverIndexer(Foo.indexer));
|
||||
const indexValue = Foo.indexer.value;
|
||||
strictEqual(indexValue.kind, "Model" as const);
|
||||
deepStrictEqual([...indexValue.properties.keys()], ["foo", "bar"]);
|
||||
});
|
||||
|
||||
it("cannot intersect model with properties and a primitive type", async () => {
|
||||
it("cannot intersect model with a scalar", async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
alias A = string & {prop1: string};
|
||||
`);
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "intersect-invalid-index",
|
||||
message: "Cannot intersect a model that cannot hold properties.",
|
||||
code: "intersect-non-model",
|
||||
message: "Cannot intersect non-model types (including union types).",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { strictEqual } from "assert";
|
||||
import { BasicTestRunner, createTestHost, createTestWrapper } from "../../testing/index.js";
|
||||
|
||||
describe("compiler: models", () => {
|
||||
let runner: BasicTestRunner;
|
||||
|
||||
beforeEach(async () => {
|
||||
const host = await createTestHost();
|
||||
runner = createTestWrapper(host, (code) => code);
|
||||
});
|
||||
|
||||
it("declare simple scalar", async () => {
|
||||
const { A } = await runner.compile(`
|
||||
@test scalar A;
|
||||
`);
|
||||
|
||||
strictEqual(A.kind, "Scalar" as const);
|
||||
strictEqual(A.name, "A");
|
||||
strictEqual(A.baseScalar, undefined);
|
||||
});
|
||||
|
||||
it("declare simple scalar extending another", async () => {
|
||||
const { A } = await runner.compile(`
|
||||
@test scalar A extends numeric;
|
||||
`);
|
||||
|
||||
strictEqual(A.kind, "Scalar" as const);
|
||||
strictEqual(A.name, "A");
|
||||
strictEqual(A.baseScalar, runner.program.checker.getStdType("numeric"));
|
||||
});
|
||||
|
||||
it("declare scalar with template parameters", async () => {
|
||||
const { A } = await runner.compile(`
|
||||
@doc(T)
|
||||
@test
|
||||
scalar A<T extends string>;
|
||||
|
||||
alias B = A<"123">;
|
||||
`);
|
||||
|
||||
strictEqual(A.kind, "Scalar" as const);
|
||||
strictEqual(A.name, "A");
|
||||
});
|
||||
});
|
|
@ -65,7 +65,7 @@ describe("compiler: spread", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("emit diagnostic if spreading type not allowing properties", async () => {
|
||||
it("emit diagnostic if spreading scalar type", async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
model Foo {
|
||||
...string
|
||||
|
@ -74,7 +74,7 @@ describe("compiler: spread", () => {
|
|||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "spread-model",
|
||||
message: "Cannot spread type because it cannot hold properties.",
|
||||
message: "Cannot spread properties of non-model type.",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ describe("compiler: templates", () => {
|
|||
|
||||
const { A } = (await testHost.compile("main.cadl")) as { A: Model };
|
||||
const a = A.properties.get("a")!;
|
||||
strictEqual(a.type.kind, "Model");
|
||||
strictEqual(a.type.kind, "Scalar");
|
||||
strictEqual((a.type as Model).name, "string");
|
||||
});
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ describe("compiler: union declarations", () => {
|
|||
const { Foo } = (await testHost.compile("./")) as { Foo: Union };
|
||||
ok(Foo);
|
||||
ok(blues.has(Foo));
|
||||
strictEqual(Foo.options.length, 2);
|
||||
strictEqual(Foo.variants.size, 2);
|
||||
const varX = Foo.variants.get("x")!;
|
||||
ok(blues.has(varX));
|
||||
const varY = Foo.variants.get("y")!;
|
||||
|
@ -37,8 +37,8 @@ describe("compiler: union declarations", () => {
|
|||
strictEqual(varX.kind, "UnionVariant");
|
||||
strictEqual(varY.kind, "UnionVariant");
|
||||
|
||||
strictEqual(varXType.kind, "Model");
|
||||
strictEqual(varYType.kind, "Model");
|
||||
strictEqual(varXType.kind, "Scalar");
|
||||
strictEqual(varYType.kind, "Scalar");
|
||||
});
|
||||
|
||||
it("can be templated", async () => {
|
||||
|
@ -56,7 +56,7 @@ describe("compiler: union declarations", () => {
|
|||
const varXType = (varX as UnionVariant).type as Model;
|
||||
|
||||
strictEqual(varX.kind, "UnionVariant");
|
||||
strictEqual(varXType.kind, "Model");
|
||||
strictEqual(varXType.kind, "Scalar");
|
||||
strictEqual(varXType.name, "int32");
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { deepStrictEqual, ok, strictEqual } from "assert";
|
||||
import { getVisibility, isSecret, Model, Operation } from "../../core/index.js";
|
||||
import { getVisibility, isSecret, Model, Operation, Scalar } from "../../core/index.js";
|
||||
import {
|
||||
getDoc,
|
||||
getFriendlyName,
|
||||
|
@ -194,13 +194,13 @@ describe("compiler: built-in decorators", () => {
|
|||
});
|
||||
|
||||
describe("@knownValues", () => {
|
||||
it("assign the known values to string model", async () => {
|
||||
it("assign the known values to string scalar", async () => {
|
||||
const { Bar } = (await runner.compile(`
|
||||
enum Foo {one: "one", two: "two"}
|
||||
@test
|
||||
@knownValues(Foo)
|
||||
model Bar is string {}
|
||||
`)) as { Bar: Model };
|
||||
scalar Bar extends string;
|
||||
`)) as { Bar: Scalar };
|
||||
|
||||
ok(Bar.kind);
|
||||
const knownValues = getKnownValues(runner.program, Bar);
|
||||
|
@ -208,7 +208,7 @@ describe("compiler: built-in decorators", () => {
|
|||
strictEqual(knownValues.kind, "Enum");
|
||||
});
|
||||
|
||||
it("assign the known values to number model", async () => {
|
||||
it("assign the known values to number scalar", async () => {
|
||||
const { Bar } = (await runner.compile(`
|
||||
enum Foo {
|
||||
one: 1;
|
||||
|
@ -216,8 +216,8 @@ describe("compiler: built-in decorators", () => {
|
|||
}
|
||||
@test
|
||||
@knownValues(Foo)
|
||||
model Bar is int32 {}
|
||||
`)) as { Bar: Model };
|
||||
scalar Bar extends int32;
|
||||
`)) as { Bar: Scalar };
|
||||
|
||||
ok(Bar.kind);
|
||||
const knownValues = getKnownValues(runner.program, Bar);
|
||||
|
@ -235,7 +235,7 @@ describe("compiler: built-in decorators", () => {
|
|||
expectDiagnostics(diagnostics, {
|
||||
code: "decorator-wrong-target",
|
||||
message:
|
||||
"Cannot apply @knownValues decorator to type it is not one of: string, int8, int16, int32, int64, float32, float64",
|
||||
"Cannot apply @knownValues decorator to Bar since it is not assignable to Cadl.string | Cadl.numeric | Cadl.Reflection.ModelProperty",
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -246,12 +246,12 @@ describe("compiler: built-in decorators", () => {
|
|||
two: 2;
|
||||
}
|
||||
@knownValues(Foo)
|
||||
model Bar is string {}
|
||||
scalar Bar extends string;
|
||||
`);
|
||||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "known-values-invalid-enum",
|
||||
message: "Enum cannot be used on this type. Member one is not assignable to type string.",
|
||||
message: "Enum cannot be used on this type. Member one is not assignable to type Bar.",
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -265,7 +265,7 @@ describe("compiler: built-in decorators", () => {
|
|||
expectDiagnostics(diagnostics, {
|
||||
code: "decorator-wrong-target",
|
||||
message:
|
||||
"Cannot apply @knownValues decorator to type it is not one of: string, int8, int16, int32, int64, float32, float64",
|
||||
"Cannot apply @knownValues decorator to Bar since it is not assignable to Cadl.string | Cadl.numeric | Cadl.Reflection.ModelProperty",
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -273,7 +273,7 @@ describe("compiler: built-in decorators", () => {
|
|||
const diagnostics = await runner.diagnose(`
|
||||
model Foo {}
|
||||
@knownValues(Foo)
|
||||
model Bar is string {}
|
||||
scalar Bar extends string;
|
||||
`);
|
||||
|
||||
expectDiagnostics(diagnostics, {
|
||||
|
@ -728,7 +728,7 @@ describe("compiler: built-in decorators", () => {
|
|||
`
|
||||
@test
|
||||
@secret
|
||||
model A is string;
|
||||
scalar A extends string;
|
||||
`
|
||||
);
|
||||
|
||||
|
@ -752,7 +752,7 @@ describe("compiler: built-in decorators", () => {
|
|||
it("can be applied on a model property with stringlike model as type", async () => {
|
||||
const { A } = (await runner.compile(
|
||||
`
|
||||
model CustomStr is string;
|
||||
scalar CustomStr extends string;
|
||||
|
||||
@test
|
||||
model A {
|
||||
|
@ -776,7 +776,8 @@ describe("compiler: built-in decorators", () => {
|
|||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "decorator-wrong-target",
|
||||
message: "Cannot apply @secret decorator to type it is not one of: string",
|
||||
message:
|
||||
"Cannot apply @secret decorator to A since it is not assignable to Cadl.string | Cadl.Reflection.ModelProperty",
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -785,13 +786,14 @@ describe("compiler: built-in decorators", () => {
|
|||
`
|
||||
@test
|
||||
@secret
|
||||
model A is int32 {}
|
||||
scalar A extends int32;
|
||||
`
|
||||
);
|
||||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "decorator-wrong-target",
|
||||
message: "Cannot apply @secret decorator to type it is not one of: string",
|
||||
message:
|
||||
"Cannot apply @secret decorator to A since it is not assignable to Cadl.string | Cadl.Reflection.ModelProperty",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -306,6 +306,59 @@ model Foo {
|
|||
});
|
||||
});
|
||||
|
||||
describe("scalar", () => {
|
||||
it("format on single line", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
scalar
|
||||
Foo
|
||||
`,
|
||||
expected: `
|
||||
scalar Foo;
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it("format with extends", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
scalar
|
||||
Foo extends
|
||||
string
|
||||
`,
|
||||
expected: `
|
||||
scalar Foo extends string;
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it("format with template parameters", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
scalar
|
||||
Foo<K,
|
||||
V>
|
||||
`,
|
||||
expected: `
|
||||
scalar Foo<K, V>;
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it("format with decorator", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
@some @decorator
|
||||
scalar Foo
|
||||
`,
|
||||
expected: `
|
||||
@some
|
||||
@decorator
|
||||
scalar Foo;
|
||||
`,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("comments", () => {
|
||||
it("format comment at position 0", () => {
|
||||
assertFormat({
|
||||
|
|
|
@ -222,7 +222,7 @@ describe("compiler: discriminator", () => {
|
|||
expectDiagnostics(diagnostics, {
|
||||
code: "invalid-discriminator-value",
|
||||
message:
|
||||
"Discriminator value should be a string, union of string or string enum but was Model.",
|
||||
"Discriminator value should be a string, union of string or string enum but was Scalar.",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ type Tokenize = (input: string) => Promise<Token[]>;
|
|||
const Token = {
|
||||
keywords: {
|
||||
model: createToken("model", "keyword.other.cadl"),
|
||||
scalar: createToken("scalar", "keyword.other.cadl"),
|
||||
operation: createToken("op", "keyword.other.cadl"),
|
||||
namespace: createToken("namespace", "keyword.other.cadl"),
|
||||
interface: createToken("interface", "keyword.other.cadl"),
|
||||
|
@ -538,6 +539,40 @@ function testColorization(description: string, tokenize: Tokenize) {
|
|||
});
|
||||
});
|
||||
|
||||
describe("scalar", () => {
|
||||
it("simple scalar", async () => {
|
||||
const tokens = await tokenize("scalar Foo;");
|
||||
deepStrictEqual(tokens, [
|
||||
Token.keywords.scalar,
|
||||
Token.identifiers.type("Foo"),
|
||||
Token.punctuation.semicolon,
|
||||
]);
|
||||
});
|
||||
|
||||
it("scalar with extends", async () => {
|
||||
const tokens = await tokenize("scalar Foo extends Bar;");
|
||||
deepStrictEqual(tokens, [
|
||||
Token.keywords.scalar,
|
||||
Token.identifiers.type("Foo"),
|
||||
Token.keywords.extends,
|
||||
Token.identifiers.type("Bar"),
|
||||
Token.punctuation.semicolon,
|
||||
]);
|
||||
});
|
||||
|
||||
it("single template argument model", async () => {
|
||||
const tokens = await tokenize("scalar Foo<T>;");
|
||||
deepStrictEqual(tokens, [
|
||||
Token.keywords.scalar,
|
||||
Token.identifiers.type("Foo"),
|
||||
Token.punctuation.typeParameters.begin,
|
||||
Token.identifiers.type("T"),
|
||||
Token.punctuation.typeParameters.end,
|
||||
Token.punctuation.semicolon,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("namespaces", () => {
|
||||
it("simple global namespace", async () => {
|
||||
const tokens = await tokenize("namespace Foo;");
|
||||
|
|
|
@ -23,8 +23,8 @@ describe("compiler: server: completion", () => {
|
|||
{
|
||||
label: "int32",
|
||||
insertText: "int32",
|
||||
kind: CompletionItemKind.Keyword,
|
||||
documentation: { kind: MarkupKind.Markdown, value: "```cadl\nmodel Cadl.int32\n```" },
|
||||
kind: CompletionItemKind.Unit,
|
||||
documentation: { kind: MarkupKind.Markdown, value: "```cadl\nscalar Cadl.int32\n```" },
|
||||
},
|
||||
{
|
||||
label: "Record",
|
||||
|
@ -263,8 +263,8 @@ describe("compiler: server: completion", () => {
|
|||
{
|
||||
label: "string",
|
||||
insertText: "string",
|
||||
kind: CompletionItemKind.Keyword,
|
||||
documentation: { kind: MarkupKind.Markdown, value: "```cadl\nmodel Cadl.string\n```" },
|
||||
kind: CompletionItemKind.Unit,
|
||||
documentation: { kind: MarkupKind.Markdown, value: "```cadl\nscalar Cadl.string\n```" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -255,6 +255,7 @@ async function createTestHostInternal(): Promise<TestHost> {
|
|||
if (!name) {
|
||||
if (
|
||||
target.kind === "Model" ||
|
||||
target.kind === "Scalar" ||
|
||||
target.kind === "Namespace" ||
|
||||
target.kind === "Enum" ||
|
||||
target.kind === "Operation" ||
|
||||
|
|
|
@ -31,6 +31,8 @@ export function shouldInline(program: Program, type: Type): boolean {
|
|||
switch (type.kind) {
|
||||
case "Model":
|
||||
return !type.name || isTemplateInstance(type);
|
||||
case "Scalar":
|
||||
return program.checker.isStdType(type) || isTemplateInstance(type);
|
||||
case "Enum":
|
||||
case "Union":
|
||||
return !type.name;
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
getDiscriminator,
|
||||
getDoc,
|
||||
getFormat,
|
||||
getIntrinsicModelName,
|
||||
getKnownValues,
|
||||
getMaxItems,
|
||||
getMaxLength,
|
||||
|
@ -24,11 +23,12 @@ import {
|
|||
getService,
|
||||
getSummary,
|
||||
ignoreDiagnostics,
|
||||
IntrinsicScalarName,
|
||||
IntrinsicType,
|
||||
isErrorType,
|
||||
isGlobalNamespace,
|
||||
isIntrinsic,
|
||||
isNeverType,
|
||||
isNullType,
|
||||
isNumericType,
|
||||
isSecret,
|
||||
isStringType,
|
||||
|
@ -45,6 +45,7 @@ import {
|
|||
ProjectionApplication,
|
||||
projectProgram,
|
||||
resolvePath,
|
||||
Scalar,
|
||||
Service,
|
||||
StringLiteral,
|
||||
TwoLevelMap,
|
||||
|
@ -257,14 +258,18 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
tags = new Set();
|
||||
}
|
||||
|
||||
// Todo: Should be able to replace with isRelatedTo(prop.type, "string") https://github.com/microsoft/cadl/pull/571
|
||||
function isValidServerVariableType(program: Program, type: Type): boolean {
|
||||
switch (type.kind) {
|
||||
case "String":
|
||||
return true;
|
||||
case "Model":
|
||||
const name = getIntrinsicModelName(program, type);
|
||||
return name === "string";
|
||||
case "Union":
|
||||
case "Scalar":
|
||||
return ignoreDiagnostics(
|
||||
program.checker.isTypeAssignableTo(
|
||||
type.projectionBase ?? type,
|
||||
program.checker.getStdType("string"),
|
||||
type
|
||||
)
|
||||
);
|
||||
case "Enum":
|
||||
for (const member of type.members.values()) {
|
||||
if (member.value && typeof member.value !== "string") {
|
||||
|
@ -272,13 +277,6 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
}
|
||||
}
|
||||
return true;
|
||||
case "Union":
|
||||
for (const option of type.options) {
|
||||
if (!isValidServerVariableType(program, option)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -485,7 +483,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
|
||||
function isBinaryPayload(body: Type, contentType: string) {
|
||||
return (
|
||||
body.kind === "Model" &&
|
||||
body.kind === "Scalar" &&
|
||||
body.name === "bytes" &&
|
||||
contentType !== "application/json" &&
|
||||
contentType !== "text/plain"
|
||||
|
@ -561,15 +559,8 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
};
|
||||
}
|
||||
|
||||
if (type.kind === "Model" && type.name === getIntrinsicModelName(program, type)) {
|
||||
// if the model is one of the Cadl Intrinsic type.
|
||||
// it's a base Cadl "primitive" that corresponds directly to an OpenAPI
|
||||
// primitive. In such cases, we don't want to emit a ref and instead just
|
||||
// emit the base type directly.
|
||||
const builtIn = mapCadlIntrinsicModelToOpenAPI(type, visibility);
|
||||
if (builtIn !== undefined) {
|
||||
return builtIn;
|
||||
}
|
||||
if (type.kind === "Scalar" && program.checker.isStdType(type)) {
|
||||
return getSchemaForScalar(type);
|
||||
}
|
||||
|
||||
if (type.kind === "String" || type.kind === "Number" || type.kind === "Boolean") {
|
||||
|
@ -796,6 +787,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
namespace,
|
||||
{
|
||||
model: (x) => x.name !== "" && computeSchema(x),
|
||||
scalar: computeSchema,
|
||||
enum: computeSchema,
|
||||
union: (x) => x.name !== undefined && computeSchema(x),
|
||||
},
|
||||
|
@ -851,6 +843,8 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
return getSchemaForIntrinsicType(type);
|
||||
case "Model":
|
||||
return getSchemaForModel(type, visibility);
|
||||
case "Scalar":
|
||||
return getSchemaForScalar(type);
|
||||
case "Union":
|
||||
return getSchemaForUnion(type, visibility);
|
||||
case "UnionVariant":
|
||||
|
@ -1017,10 +1011,6 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
return schema;
|
||||
}
|
||||
|
||||
function isNullType(type: Type): boolean {
|
||||
return isIntrinsic(program, type) && getIntrinsicModelName(program, type) === "null";
|
||||
}
|
||||
|
||||
function isLiteralType(type: Type): type is StringLiteral | NumericLiteral | BooleanLiteral {
|
||||
return type.kind === "Boolean" || type.kind === "String" || type.kind === "Number";
|
||||
}
|
||||
|
@ -1185,7 +1175,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
}
|
||||
|
||||
function applyIntrinsicDecorators(
|
||||
cadlType: Model | ModelProperty,
|
||||
cadlType: Scalar | ModelProperty,
|
||||
target: OpenAPI3Schema
|
||||
): OpenAPI3Schema {
|
||||
const newTarget = { ...target };
|
||||
|
@ -1286,7 +1276,7 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
if (cadlType.indexer) {
|
||||
if (isNeverType(cadlType.indexer.key)) {
|
||||
} else {
|
||||
const name = getIntrinsicModelName(program, cadlType.indexer.key);
|
||||
const name = cadlType.indexer.key.name;
|
||||
if (name === "string") {
|
||||
return {
|
||||
type: "object",
|
||||
|
@ -1300,37 +1290,46 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!isIntrinsic(program, cadlType)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSchemaForScalar(scalar: Scalar): OpenAPI3Schema {
|
||||
let result: OpenAPI3Schema = {};
|
||||
if (program.checker.isStdType(scalar)) {
|
||||
result = getSchemaForStdScalars(scalar);
|
||||
} else if (scalar.baseScalar) {
|
||||
result = getSchemaForScalar(scalar.baseScalar);
|
||||
}
|
||||
const name = getIntrinsicModelName(program, cadlType);
|
||||
switch (name) {
|
||||
return applyIntrinsicDecorators(scalar, result);
|
||||
}
|
||||
|
||||
function getSchemaForStdScalars(scalar: Scalar & { name: IntrinsicScalarName }): OpenAPI3Schema {
|
||||
switch (scalar.name) {
|
||||
case "bytes":
|
||||
return { type: "string", format: "byte" };
|
||||
case "int8":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "int8" });
|
||||
return { type: "integer", format: "int8" };
|
||||
case "int16":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "int16" });
|
||||
return { type: "integer", format: "int16" };
|
||||
case "int32":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "int32" });
|
||||
return { type: "integer", format: "int32" };
|
||||
case "int64":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "int64" });
|
||||
return { type: "integer", format: "int64" };
|
||||
case "safeint":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "int64" });
|
||||
return { type: "integer", format: "int64" };
|
||||
case "uint8":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "uint8" });
|
||||
return { type: "integer", format: "uint8" };
|
||||
case "uint16":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "uint16" });
|
||||
return { type: "integer", format: "uint16" };
|
||||
case "uint32":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "uint32" });
|
||||
return { type: "integer", format: "uint32" };
|
||||
case "uint64":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "integer", format: "uint64" });
|
||||
return { type: "integer", format: "uint64" };
|
||||
case "float64":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "number", format: "double" });
|
||||
return { type: "number", format: "double" };
|
||||
case "float32":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "number", format: "float" });
|
||||
return { type: "number", format: "float" };
|
||||
case "string":
|
||||
return applyIntrinsicDecorators(cadlType, { type: "string" });
|
||||
return { type: "string" };
|
||||
case "boolean":
|
||||
return { type: "boolean" };
|
||||
case "plainDate":
|
||||
|
@ -1341,6 +1340,15 @@ function createOAPIEmitter(program: Program, options: ResolvedOpenAPI3EmitterOpt
|
|||
return { type: "string", format: "time" };
|
||||
case "duration":
|
||||
return { type: "string", format: "duration" };
|
||||
case "uri":
|
||||
return { type: "string", format: "uri" };
|
||||
case "integer":
|
||||
case "numeric":
|
||||
case "float":
|
||||
return {}; // Waiting on design for more precise type https://github.com/microsoft/cadl/issues/1260
|
||||
default:
|
||||
const _assertNever: never = scalar.name;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ describe("openapi3: discriminated unions", () => {
|
|||
},
|
||||
{
|
||||
code: "invalid-discriminator-value",
|
||||
message: `Discriminator value should be a string, union of string or string enum but was Model.`,
|
||||
message: `Discriminator value should be a string, union of string or string enum but was Scalar.`,
|
||||
},
|
||||
{
|
||||
code: "invalid-discriminator-value",
|
||||
|
@ -287,7 +287,7 @@ describe("openapi3: discriminated unions", () => {
|
|||
},
|
||||
{
|
||||
code: "invalid-discriminator-value",
|
||||
message: `Discriminator value should be a string, union of string or string enum but was Model.`,
|
||||
message: `Discriminator value should be a string, union of string or string enum but was Scalar.`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -475,7 +475,7 @@ describe("openapi3: models", () => {
|
|||
}
|
||||
|
||||
@knownValues(KnownPetType)
|
||||
model PetType is string {}
|
||||
scalar PetType extends string;
|
||||
model Pet { type: PetType };
|
||||
`
|
||||
);
|
||||
|
|
|
@ -44,7 +44,7 @@ describe("openapi3: primitives", () => {
|
|||
const res = await oapiForModel(
|
||||
"Pet",
|
||||
`
|
||||
model shortString is string {}
|
||||
scalar shortString extends string;
|
||||
model Pet { name: shortString };
|
||||
`
|
||||
);
|
||||
|
@ -57,13 +57,13 @@ describe("openapi3: primitives", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("specify attributes on model is", () => {
|
||||
describe("specify attributes on scalar is", () => {
|
||||
it("includes data passed on the model", async () => {
|
||||
const res = await oapiForModel(
|
||||
"Pet",
|
||||
`
|
||||
@maxLength(10) @minLength(10)
|
||||
model shortString is string {}
|
||||
scalar shortString extends string;
|
||||
model Pet { name: shortString };
|
||||
`
|
||||
);
|
||||
|
@ -83,9 +83,9 @@ describe("openapi3: primitives", () => {
|
|||
"Pet",
|
||||
`
|
||||
@maxLength(10)
|
||||
model shortString is string {}
|
||||
scalar shortString extends string;
|
||||
@minLength(1)
|
||||
model shortButNotEmptyString is shortString {};
|
||||
scalar shortButNotEmptyString extends shortString;
|
||||
model Pet { name: shortButNotEmptyString, breed: shortString };
|
||||
`
|
||||
);
|
||||
|
@ -110,7 +110,7 @@ describe("openapi3: primitives", () => {
|
|||
"Pet",
|
||||
`
|
||||
@extension("x-custom", "my-value")
|
||||
model Pet is string;
|
||||
scalar Pet extends string;
|
||||
`
|
||||
);
|
||||
|
||||
|
@ -128,7 +128,7 @@ describe("openapi3: primitives", () => {
|
|||
"shortString",
|
||||
`
|
||||
@doc("My custom description")
|
||||
model shortString is string {}
|
||||
scalar shortString extends string;
|
||||
`
|
||||
);
|
||||
|
||||
|
@ -145,7 +145,7 @@ describe("openapi3: primitives", () => {
|
|||
"specialint",
|
||||
`
|
||||
@doc("My custom description")
|
||||
model specialint is int32 {}
|
||||
scalar specialint extends int32;
|
||||
`
|
||||
);
|
||||
|
||||
|
@ -165,7 +165,7 @@ describe("openapi3: primitives", () => {
|
|||
"Pet",
|
||||
`
|
||||
@secret
|
||||
model Pet is string;
|
||||
scalar Pet extends string;
|
||||
`
|
||||
);
|
||||
deepStrictEqual(res.schemas.Pet, { type: "string", format: "password" });
|
||||
|
|
|
@ -20,5 +20,5 @@ extern dec action(target: Operation, name?: string);
|
|||
extern dec collectionAction(target: Operation, resourceType: object, name?: string);
|
||||
|
||||
namespace Private {
|
||||
extern dec resourceLocation(target: unknown, resourceType: object);
|
||||
extern dec resourceLocation(target: string, resourceType: object);
|
||||
}
|
||||
|
|
|
@ -6,4 +6,4 @@ namespace Cadl.Rest;
|
|||
|
||||
@doc("The location of an instance of {name}", TResource)
|
||||
@Private.resourceLocation(TResource)
|
||||
model ResourceLocation<TResource extends object> is uri;
|
||||
scalar ResourceLocation<TResource extends object> extends uri;
|
||||
|
|
|
@ -3,10 +3,9 @@ import {
|
|||
Diagnostic,
|
||||
DiagnosticCollector,
|
||||
getDoc,
|
||||
getIntrinsicModelName,
|
||||
isArrayModelType,
|
||||
isErrorModel,
|
||||
isIntrinsic,
|
||||
isNullType,
|
||||
isVoidType,
|
||||
Model,
|
||||
ModelProperty,
|
||||
|
@ -40,7 +39,7 @@ export function getResponsesForOperation(
|
|||
const responses: Record<string | symbol, HttpOperationResponse> = {};
|
||||
if (responseType.kind === "Union") {
|
||||
for (const option of responseType.variants.values()) {
|
||||
if (isNullType(program, option.type)) {
|
||||
if (isNullType(option.type)) {
|
||||
// TODO how should we treat this? https://github.com/microsoft/cadl/issues/356
|
||||
continue;
|
||||
}
|
||||
|
@ -53,10 +52,6 @@ export function getResponsesForOperation(
|
|||
return diagnostics.wrap(Object.values(responses));
|
||||
}
|
||||
|
||||
function isNullType(program: Program, type: Type): boolean {
|
||||
return isIntrinsic(program, type) && getIntrinsicModelName(program, type) === "null";
|
||||
}
|
||||
|
||||
function processResponseType(
|
||||
program: Program,
|
||||
diagnostics: DiagnosticCollector,
|
||||
|
@ -204,11 +199,7 @@ function getResponseBody(
|
|||
metadata: Set<ModelProperty>
|
||||
): Type | undefined {
|
||||
// non-model or intrinsic/array model -> response body is response type
|
||||
if (
|
||||
responseType.kind !== "Model" ||
|
||||
isIntrinsic(program, responseType) ||
|
||||
isArrayModelType(program, responseType)
|
||||
) {
|
||||
if (responseType.kind !== "Model" || isArrayModelType(program, responseType)) {
|
||||
return responseType;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
Operation,
|
||||
Program,
|
||||
reportDeprecated,
|
||||
Scalar,
|
||||
setCadlNamespace,
|
||||
Type,
|
||||
} from "@cadl-lang/compiler";
|
||||
|
@ -389,7 +390,7 @@ export function $resourceLocation(
|
|||
context.program.stateMap(resourceLocationsKey).set(entity, resourceType);
|
||||
}
|
||||
|
||||
export function getResourceLocationType(program: Program, entity: Model): Model | undefined {
|
||||
export function getResourceLocationType(program: Program, entity: Scalar): Model | undefined {
|
||||
return program.stateMap(resourceLocationsKey).get(entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Model } from "@cadl-lang/compiler";
|
||||
import { Scalar } from "@cadl-lang/compiler";
|
||||
import { BasicTestRunner, expectDiagnostics } from "@cadl-lang/compiler/testing";
|
||||
import { ok, strictEqual } from "assert";
|
||||
import { getResourceLocationType } from "../src/rest.js";
|
||||
|
@ -12,22 +12,21 @@ describe("rest: rest decorators", () => {
|
|||
});
|
||||
|
||||
describe("@resourceLocation", () => {
|
||||
// Depends on the separation between models and scalar https://github.com/microsoft/cadl/issues/1187
|
||||
it.skip("emit diagnostic when used on non-model", async () => {
|
||||
it("emit diagnostic when used on non-model", async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
model Widget {};
|
||||
|
||||
@Cadl.Rest.Private.resourceLocation(Widget)
|
||||
op test(): string;
|
||||
|
||||
model WidgetLocation is ResourceLocation<Widget>;
|
||||
scalar WidgetLocation extends ResourceLocation<Widget>;
|
||||
`);
|
||||
|
||||
expectDiagnostics(diagnostics, [
|
||||
{
|
||||
code: "decorator-wrong-target",
|
||||
message:
|
||||
"Cannot apply @resourceLocation decorator to test since it is not assignable to Cadl.object",
|
||||
"Cannot apply @resourceLocation decorator to test since it is not assignable to Cadl.string",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -37,10 +36,10 @@ describe("rest: rest decorators", () => {
|
|||
model Widget {};
|
||||
|
||||
@test
|
||||
model WidgetLocation is ResourceLocation<Widget>;
|
||||
`)) as { WidgetLocation: Model };
|
||||
scalar WidgetLocation extends ResourceLocation<Widget>;
|
||||
`)) as { WidgetLocation: Scalar };
|
||||
|
||||
const resourceType = getResourceLocationType(runner.program, WidgetLocation);
|
||||
const resourceType = getResourceLocationType(runner.program, WidgetLocation.baseScalar!);
|
||||
ok(resourceType);
|
||||
strictEqual(resourceType!.name, "Widget");
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@ The name of a book.
|
|||
Book names have the form `shelves/{shelf_id}/books/{book_id}`
|
||||
""")
|
||||
@pattern("shelves/\\w+/books/\\w+")
|
||||
model book_name is string;
|
||||
scalar book_name extends string;
|
||||
|
||||
@doc("A single book in the library.")
|
||||
model Book {
|
||||
|
@ -114,7 +114,7 @@ The name of a shelf.
|
|||
Shelf names have the form `shelves/{shelf_id}`.
|
||||
""")
|
||||
@pattern("shelves/\\w+")
|
||||
model shelf_name is string;
|
||||
scalar shelf_name extends string;
|
||||
|
||||
@doc("A Shelf contains a collection of books with a theme.")
|
||||
model Shelf {
|
||||
|
|
|
@ -1164,9 +1164,9 @@
|
|||
},
|
||||
"nextLink": {
|
||||
"type": "string",
|
||||
"description": "The link to the next page of items",
|
||||
"format": "uri",
|
||||
"x-cadl-name": "Rest.ResourceLocation<Pet>"
|
||||
"description": "The link to the next page of items",
|
||||
"x-cadl-name": "Rest.ResourceLocation"
|
||||
}
|
||||
},
|
||||
"description": "Paged response of Pet items",
|
||||
|
@ -1219,9 +1219,9 @@
|
|||
},
|
||||
"nextLink": {
|
||||
"type": "string",
|
||||
"description": "The link to the next page of items",
|
||||
"format": "uri",
|
||||
"x-cadl-name": "Rest.ResourceLocation<Checkup>"
|
||||
"description": "The link to the next page of items",
|
||||
"x-cadl-name": "Rest.ResourceLocation"
|
||||
}
|
||||
},
|
||||
"description": "Paged response of Checkup items",
|
||||
|
@ -1301,9 +1301,9 @@
|
|||
},
|
||||
"nextLink": {
|
||||
"type": "string",
|
||||
"description": "The link to the next page of items",
|
||||
"format": "uri",
|
||||
"x-cadl-name": "Rest.ResourceLocation<Toy>"
|
||||
"description": "The link to the next page of items",
|
||||
"x-cadl-name": "Rest.ResourceLocation"
|
||||
}
|
||||
},
|
||||
"description": "Paged response of Toy items",
|
||||
|
@ -1375,9 +1375,9 @@
|
|||
},
|
||||
"nextLink": {
|
||||
"type": "string",
|
||||
"description": "The link to the next page of items",
|
||||
"format": "uri",
|
||||
"x-cadl-name": "Rest.ResourceLocation<Owner>"
|
||||
"description": "The link to the next page of items",
|
||||
"x-cadl-name": "Rest.ResourceLocation"
|
||||
}
|
||||
},
|
||||
"description": "Paged response of Owner items",
|
||||
|
|
|
@ -275,6 +275,7 @@ StatementList :
|
|||
|
||||
Statement :
|
||||
ModelStatement
|
||||
ScalarStatement
|
||||
InterfaceStatement
|
||||
NamespaceStatement
|
||||
OperationStatement
|
||||
|
@ -296,6 +297,12 @@ ModelStatement :
|
|||
IsModelHeritage :
|
||||
`is` Expression
|
||||
|
||||
ScalarStatement :
|
||||
DecoratorList? `scalar` Identifier TemplateParameters? ScalarExtends `;`
|
||||
|
||||
ScalarExtends :
|
||||
`extends` Expression
|
||||
|
||||
ExtendsModelHeritage :
|
||||
`extends` Expression
|
||||
|
||||
|
|
|
@ -9,16 +9,11 @@ Cadl models are used to describe data shapes or schemas.
|
|||
|
||||
## Model kinds
|
||||
|
||||
Models can be used to represent 3 types:
|
||||
Models can be used to represent 2 types:
|
||||
|
||||
- [Primitives](#primitives)
|
||||
- [Record](#record)
|
||||
- [Array](#array)
|
||||
|
||||
### Primitives
|
||||
|
||||
These are types without any fields(For example `string`, `int32`, `boolean`, etc.)
|
||||
|
||||
### Record
|
||||
|
||||
Record models are structure with named fields called properties.
|
||||
|
|
|
@ -49,6 +49,16 @@ _Details: [Decorators](./decorators.md)_
|
|||
| Save state in decorator | `context.program.stateMap(key).set(target, <value>)` |
|
||||
| Augment decorator | `@@tag(MyType, "abc")` |
|
||||
|
||||
## Scalars
|
||||
|
||||
_Details: [Scalars](./models.md)_
|
||||
|
||||
| Feature | Example |
|
||||
| ------------------ | ------------------------------------------- |
|
||||
| Scalar declaration | `scalar ternary` |
|
||||
| Extend scalar | `scalar Password extends string` |
|
||||
| Template scalar | `@doc(T) scalar Password<T extends string>` |
|
||||
|
||||
## Models
|
||||
|
||||
_Details: [Models](./models.md)_
|
||||
|
@ -57,7 +67,7 @@ _Details: [Models](./models.md)_
|
|||
| ------------------------------ | ------------------------------------- |
|
||||
| Model declaration | `model Pet {}` |
|
||||
| Model inheritance | `model Dog extends Pet {}` |
|
||||
| Model is | `model uuid is string;` |
|
||||
| scalar is | `model uuid extends string;` |
|
||||
| Model spread | `model Dog {...Animal}` |
|
||||
| Property | `model Dog { name: string }` |
|
||||
| Optional property | `model Dog { owner?: string }` |
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: Scalars
|
||||
---
|
||||
|
||||
# Scalars
|
||||
|
||||
These are types without any fields(For example `string`, `int32`, `boolean`, etc.)
|
||||
|
||||
Scalar can be declared using the `scalar` keyword
|
||||
|
||||
```cadl
|
||||
scalar ternary;
|
||||
```
|
||||
|
||||
## Extend another scalar
|
||||
|
||||
Scalar can be extended using the `extends` keyword.
|
||||
|
||||
```cadl
|
||||
scalar Password extends string;
|
||||
```
|
||||
|
||||
## Template scalar
|
||||
|
||||
Scalar support template parameters. Note: the only use for those template are decorators.
|
||||
|
||||
```cadl
|
||||
@doc(T)
|
||||
scalar Unreal<T extends string>;
|
||||
```
|
|
@ -194,7 +194,7 @@ enum OperationStateValues {
|
|||
}
|
||||
|
||||
@knownValues(OperationStateValues)
|
||||
model OperationState is string;
|
||||
scalar OperationState extends string;
|
||||
```
|
||||
|
||||
### `@secret`
|
||||
|
@ -209,7 +209,7 @@ model OperationState is string;
|
|||
|
||||
```cadl
|
||||
@secret
|
||||
model Password is string;
|
||||
scalar Password extends string;
|
||||
```
|
||||
|
||||
`@secret` can only be applied to string model;
|
||||
|
@ -219,7 +219,7 @@ model Password is string;
|
|||
```cadl
|
||||
@minLength(<integer>)
|
||||
@maxLength(<integer>)
|
||||
model Name is string;
|
||||
scalar Name extends string;
|
||||
```
|
||||
|
||||
Specify the min and max length of the string.
|
||||
|
@ -228,7 +228,7 @@ Specify the min and max length of the string.
|
|||
// Say that the name must be between 2 and 20 charchater long
|
||||
@minLength(2)
|
||||
@maxLength(20)
|
||||
model Name is string;
|
||||
scalar Name extends string;
|
||||
```
|
||||
|
||||
The decorators can also be used on model properties
|
||||
|
|
|
@ -33,6 +33,7 @@ const sidebars = {
|
|||
"language-basics/imports",
|
||||
"language-basics/namespaces",
|
||||
"language-basics/decorators",
|
||||
"language-basics/scalars",
|
||||
"language-basics/models",
|
||||
"language-basics/operations",
|
||||
"language-basics/interfaces",
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
|
||||
boolean: /\b(?:false|true)\b/,
|
||||
keyword:
|
||||
/\b(?:import|model|namespace|op|interface|union|using|is|extends|enum|alias|return|void|never|if|else|projection)\b/,
|
||||
/\b(?:import|model|scalar|namespace|op|interface|union|using|is|extends|enum|alias|return|void|never|if|else|projection)\b/,
|
||||
|
||||
function: /\b[a-z_]\w*(?=[ \t]*\()/i,
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче