From bc7ef121c5cc2d69c5ddbe674ddeb4681ca6ccc0 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:12:11 -0800 Subject: [PATCH 1/4] fix: stricter binary operation operand type compatibility check --- .../validator/expression-validator.ts | 15 ++++++++ .../tests/regression/issue-804.test.ts | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/integration/tests/regression/issue-804.test.ts diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index a4d4283e0..a5ad52ad5 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,20 @@ export default class ExpressionValidator implements AstValidator { break; } + 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/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'); + }); +}); From 3cbd8c988ef582a663774c9e63eebffa47af2fe7 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 22 Nov 2023 19:49:58 -0800 Subject: [PATCH 2/4] fix tests --- .../src/language-server/validator/expression-validator.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index a5ad52ad5..474f918b9 100644 --- a/packages/schema/src/language-server/validator/expression-validator.ts +++ b/packages/schema/src/language-server/validator/expression-validator.ts @@ -125,6 +125,14 @@ 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' From 0717e7f7985949f27effb41a6d4a2f7940998187 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:05:19 -0800 Subject: [PATCH 3/4] marks auth() member access nullable --- packages/schema/src/language-server/zmodel-linker.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index de6afc9e5..36df8d03f 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -36,7 +36,7 @@ import { isReferenceExpr, isStringLiteral, } from '@zenstackhq/language/ast'; -import { getContainingModel, hasAttribute, isFromStdlib } from '@zenstackhq/sdk'; +import { getContainingModel, hasAttribute, isFromStdlib, isIdField } from '@zenstackhq/sdk'; import { AstNode, AstNodeDescription, @@ -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) && !isIdField(node.member.ref)) { + // member access on auth() function is nullable (except for id field) + // because user may not have provided all fields + node.$resolvedType.nullable = true; + } } } } From 59a396157c0c5f015cbda4eb6fc9d3afa89a1da0 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:29:41 -0800 Subject: [PATCH 4/4] update --- packages/schema/src/language-server/zmodel-linker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 36df8d03f..23ada3d73 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -36,7 +36,7 @@ import { isReferenceExpr, isStringLiteral, } from '@zenstackhq/language/ast'; -import { getContainingModel, hasAttribute, isFromStdlib, isIdField } from '@zenstackhq/sdk'; +import { getContainingModel, hasAttribute, isFromStdlib } from '@zenstackhq/sdk'; import { AstNode, AstNodeDescription, @@ -338,8 +338,8 @@ export class ZModelLinker extends DefaultLinker { if (node.member.ref) { this.resolveToDeclaredType(node, node.member.ref.type); - if (node.$resolvedType && isAuthInvocation(node.operand) && !isIdField(node.member.ref)) { - // member access on auth() function is nullable (except for id field) + 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; }