diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index a4d4283e0..474f918b9 100644 --- a/packages/schema/src/language-server/validator/expression-validator.ts +++ b/packages/schema/src/language-server/validator/expression-validator.ts @@ -11,6 +11,7 @@ import { isDataModelFieldReference } from '@zenstackhq/sdk'; import { ValidationAcceptor } from 'langium'; import { isAuthInvocation, isCollectionPredicate } from '../../utils/ast-utils'; import { AstValidator } from '../types'; +import { typeAssignable } from './utils'; /** * Validates expressions. @@ -124,6 +125,28 @@ export default class ExpressionValidator implements AstValidator { break; } + if ( + (expr.left.$resolvedType?.nullable && isNullExpr(expr.right)) || + (expr.right.$resolvedType?.nullable && isNullExpr(expr.left)) + ) { + // comparing nullable field with null + return; + } + + if ( + typeof expr.left.$resolvedType?.decl === 'string' && + typeof expr.right.$resolvedType?.decl === 'string' + ) { + // scalar types assignability + if ( + !typeAssignable(expr.left.$resolvedType.decl, expr.right.$resolvedType.decl) && + !typeAssignable(expr.right.$resolvedType.decl, expr.left.$resolvedType.decl) + ) { + accept('error', 'incompatible operand types', { node: expr }); + } + return; + } + // disallow comparing model type with scalar type or comparison between // incompatible model types const leftType = expr.left.$resolvedType?.decl; diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index de6afc9e5..23ada3d73 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -53,7 +53,7 @@ import { } from 'langium'; import { match } from 'ts-pattern'; import { CancellationToken } from 'vscode-jsonrpc'; -import { getAllDeclarationsFromImports } from '../utils/ast-utils'; +import { getAllDeclarationsFromImports, isAuthInvocation } from '../utils/ast-utils'; import { mapBuiltinTypeToExpressionType } from './validator/utils'; interface DefaultReference extends Reference { @@ -337,6 +337,12 @@ export class ZModelLinker extends DefaultLinker { this.linkReference(node, 'member', document, [provider], true); if (node.member.ref) { this.resolveToDeclaredType(node, node.member.ref.type); + + if (node.$resolvedType && isAuthInvocation(node.operand)) { + // member access on auth() function is nullable + // because user may not have provided all fields + node.$resolvedType.nullable = true; + } } } } diff --git a/tests/integration/tests/regression/issue-804.test.ts b/tests/integration/tests/regression/issue-804.test.ts new file mode 100644 index 000000000..18659b1ec --- /dev/null +++ b/tests/integration/tests/regression/issue-804.test.ts @@ -0,0 +1,35 @@ +import { loadModelWithError } from '@zenstackhq/testtools'; + +describe('Regression: issue 804', () => { + it('regression', async () => { + expect( + await loadModelWithError( + ` + generator client { + provider = "prisma-client-js" + } + + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + } + + model User { + id Int @id @default(autoincrement()) + email Int + posts Post[] + } + + model Post { + id Int @id @default(autoincrement()) + author User? @relation(fields: [authorId], references: [id]) + authorId Int + published Boolean + + @@allow('all', auth().posts?[published] == 'TRUE') + } + ` + ) + ).toContain('incompatible operand types'); + }); +});