This commit is contained in:
Timothee Guerin 2022-11-29 09:09:39 -08:00 коммит произвёл GitHub
Родитель 6a55d933c7
Коммит 607962e49c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
60 изменённых файлов: 1261 добавлений и 961 удалений

Просмотреть файл

@ -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"
}

531
docs/spec.html сгенерированный

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -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,