Skip to content

Commit 275ad5e

Browse files
authored
feat: error if simple names of builtin declarations collide (#678)
Closes #672 ### Summary of Changes <!-- Please provide a summary of changes in this pull request, ensuring all changes are explained. -->
1 parent e846b59 commit 275ad5e

12 files changed

+150
-46
lines changed

src/language/builtins/packageNames.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BUILTINS_ROOT_PACKAGE = 'safeds';

src/language/lsp/safe-ds-node-kind-provider.ts

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
SdsPipeline,
1616
SdsPlaceholder,
1717
SdsResult,
18+
SdsSchema,
1819
SdsSegment,
1920
SdsTypeParameter,
2021
} from '../generated/ast.js';
@@ -57,6 +58,8 @@ export class SafeDsNodeKindProvider implements NodeKindProvider {
5758
/* c8 ignore next 2 */
5859
case SdsResult:
5960
return SymbolKind.Variable;
61+
case SdsSchema:
62+
return SymbolKind.Struct;
6063
case SdsSegment:
6164
return SymbolKind.Function;
6265
/* c8 ignore next 2 */

src/language/lsp/safe-ds-semantic-token-provider.ts

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isSdsPipeline,
2525
isSdsPlaceholder,
2626
isSdsReference,
27+
isSdsSchema,
2728
isSdsSegment,
2829
isSdsTypeArgument,
2930
isSdsTypeParameter,
@@ -206,6 +207,11 @@ export class SafeDsSemanticTokenProvider extends AbstractSemanticTokenProvider {
206207
type: SemanticTokenTypes.variable,
207208
modifier: [SemanticTokenModifiers.readonly, ...additionalModifiers],
208209
};
210+
} else if (isSdsSchema(node)) {
211+
return {
212+
type: SemanticTokenTypes.type,
213+
modifier: additionalModifiers,
214+
};
209215
} else if (isSdsSegment(node)) {
210216
return {
211217
type: SemanticTokenTypes.function,

src/language/validation/names.ts

+35-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {
22
isSdsQualifiedImport,
33
SdsAnnotation,
4+
SdsAttribute,
45
SdsBlockLambda,
6+
SdsBlockLambdaResult,
57
SdsCallableType,
68
SdsClass,
79
SdsDeclaration,
@@ -11,11 +13,15 @@ import {
1113
SdsFunction,
1214
SdsImportedDeclaration,
1315
SdsModule,
16+
SdsParameter,
1417
SdsPipeline,
18+
SdsPlaceholder,
19+
SdsResult,
1520
SdsSchema,
1621
SdsSegment,
22+
SdsTypeParameter,
1723
} from '../generated/ast.js';
18-
import { getDocument, ValidationAcceptor } from 'langium';
24+
import { AstNodeDescription, getDocument, ValidationAcceptor } from 'langium';
1925
import {
2026
getColumns,
2127
getEnumVariants,
@@ -37,6 +43,7 @@ import { declarationIsAllowedInPipelineFile, declarationIsAllowedInStubFile } fr
3743
import { SafeDsServices } from '../safe-ds-module.js';
3844
import { listBuiltinFiles } from '../builtins/fileFinder.js';
3945
import { CODEGEN_PREFIX } from '../../cli/generator.js';
46+
import { BUILTINS_ROOT_PACKAGE } from '../builtins/packageNames.js';
4047

4148
export const CODE_NAME_CODEGEN_PREFIX = 'name/codegen-prefix';
4249
export const CODE_NAME_CASING = 'name/casing';
@@ -67,21 +74,21 @@ export const nameMustNotStartWithCodegenPrefix = (node: SdsDeclaration, accept:
6774

6875
export const nameShouldHaveCorrectCasing = (node: SdsDeclaration, accept: ValidationAcceptor): void => {
6976
switch (node.$type) {
70-
case 'SdsAnnotation':
77+
case SdsAnnotation:
7178
return nameShouldBeUpperCamelCase(node, 'annotations', accept);
72-
case 'SdsAttribute':
79+
case SdsAttribute:
7380
return nameShouldBeLowerCamelCase(node, 'attributes', accept);
74-
case 'SdsBlockLambdaResult':
81+
case SdsBlockLambdaResult:
7582
return nameShouldBeLowerCamelCase(node, 'block lambda results', accept);
76-
case 'SdsClass':
83+
case SdsClass:
7784
return nameShouldBeUpperCamelCase(node, 'classes', accept);
78-
case 'SdsEnum':
85+
case SdsEnum:
7986
return nameShouldBeUpperCamelCase(node, 'enums', accept);
80-
case 'SdsEnumVariant':
87+
case SdsEnumVariant:
8188
return nameShouldBeUpperCamelCase(node, 'enum variants', accept);
82-
case 'SdsFunction':
89+
case SdsFunction:
8390
return nameShouldBeLowerCamelCase(node, 'functions', accept);
84-
case 'SdsModule':
91+
case SdsModule:
8592
const name = node.name ?? '';
8693
const segments = name.split('.');
8794
if (name !== '' && segments.every((it) => it !== '') && !segments.every(isLowerCamelCase)) {
@@ -92,19 +99,19 @@ export const nameShouldHaveCorrectCasing = (node: SdsDeclaration, accept: Valida
9299
});
93100
}
94101
return;
95-
case 'SdsParameter':
102+
case SdsParameter:
96103
return nameShouldBeLowerCamelCase(node, 'parameters', accept);
97-
case 'SdsPipeline':
104+
case SdsPipeline:
98105
return nameShouldBeLowerCamelCase(node, 'pipelines', accept);
99-
case 'SdsPlaceholder':
106+
case SdsPlaceholder:
100107
return nameShouldBeLowerCamelCase(node, 'placeholders', accept);
101-
case 'SdsResult':
108+
case SdsResult:
102109
return nameShouldBeLowerCamelCase(node, 'results', accept);
103-
case 'SdsSchema':
110+
case SdsSchema:
104111
return nameShouldBeUpperCamelCase(node, 'schemas', accept);
105-
case 'SdsSegment':
112+
case SdsSegment:
106113
return nameShouldBeLowerCamelCase(node, 'segments', accept);
107-
case 'SdsTypeParameter':
114+
case SdsTypeParameter:
108115
return nameShouldBeUpperCamelCase(node, 'type parameters', accept);
109116
}
110117
/* c8 ignore next */
@@ -224,7 +231,17 @@ export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsSe
224231

225232
for (const member of getModuleMembers(node)) {
226233
const packageName = getPackageName(member) ?? '';
227-
const declarationsInPackage = packageManager.getDeclarationsInPackage(packageName);
234+
235+
let declarationsInPackage: AstNodeDescription[];
236+
let kind: string;
237+
if (packageName.startsWith(BUILTINS_ROOT_PACKAGE)) {
238+
// For a builtin package the simple names of declarations must be unique
239+
declarationsInPackage = packageManager.getDeclarationsInPackageOrSubpackage(BUILTINS_ROOT_PACKAGE);
240+
kind = 'builtin declarations';
241+
} else {
242+
declarationsInPackage = packageManager.getDeclarationsInPackage(packageName);
243+
kind = 'declarations in this package';
244+
}
228245

229246
if (
230247
declarationsInPackage.some(
@@ -234,7 +251,7 @@ export const moduleMemberMustHaveNameThatIsUniqueInPackage = (services: SafeDsSe
234251
!builtinUris.has(it.documentUri.toString()),
235252
)
236253
) {
237-
accept('error', `Multiple declarations in this package have the name '${member.name}'.`, {
254+
accept('error', `Multiple ${kind} have the name '${member.name}'.`, {
238255
node: member,
239256
property: 'name',
240257
code: CODE_NAME_DUPLICATE,

src/language/validation/other/modules.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ValidationAcceptor } from 'langium';
22
import { isSdsDeclaration, isSdsPipeline, isSdsSegment, SdsDeclaration, SdsModule } from '../../generated/ast.js';
33
import { isInPipelineFile, isInStubFile } from '../../helpers/fileExtensions.js';
44
import { getModuleMembers } from '../../helpers/nodeProperties.js';
5+
import { BUILTINS_ROOT_PACKAGE } from '../../builtins/packageNames.js';
56

67
export const CODE_MODULE_FORBIDDEN_IN_PIPELINE_FILE = 'module/forbidden-in-pipeline-file';
78
export const CODE_MODULE_FORBIDDEN_IN_STUB_FILE = 'module/forbidden-in-stub-file';
@@ -56,8 +57,8 @@ export const moduleWithDeclarationsMustStatePackage = (node: SdsModule, accept:
5657
};
5758

5859
export const pipelineFileMustNotBeInSafedsPackage = (node: SdsModule, accept: ValidationAcceptor): void => {
59-
if (isInPipelineFile(node) && node.name?.startsWith('safeds')) {
60-
accept('error', "A pipeline file must not be in a 'safeds' package.", {
60+
if (isInPipelineFile(node) && node.name?.startsWith(BUILTINS_ROOT_PACKAGE)) {
61+
accept('error', `A pipeline file must not be in a '${BUILTINS_ROOT_PACKAGE}' package.`, {
6162
node,
6263
property: 'name',
6364
code: CODE_MODULE_PIPELINE_FILE_IN_SAFEDS_PACKAGE,

tests/language/lsp/safe-ds-document-symbol-provider.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ describe('SafeDsSemanticTokenProvider', async () => {
155155
},
156156
],
157157
},
158+
{
159+
testName: 'schema declaration',
160+
code: `
161+
schema S {
162+
"a": Int
163+
}
164+
`,
165+
expectedSymbols: [
166+
{
167+
name: 'S',
168+
kind: SymbolKind.Struct,
169+
},
170+
],
171+
},
158172
{
159173
testName: 'segment declaration',
160174
code: `

tests/language/lsp/safe-ds-semantic-token-provider.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ describe('SafeDsSemanticTokenProvider', async () => {
100100
`,
101101
expectedTokenTypes: [SemanticTokenTypes.variable],
102102
},
103+
{
104+
testName: 'schema declaration',
105+
code: 'schema <|S|>() {}',
106+
expectedTokenTypes: [SemanticTokenTypes.type],
107+
},
103108
{
104109
testName: 'segment declaration',
105110
code: 'segment <|s|>() {}',
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package tests.validation.names.acrossFiles.other
22

3-
annotation UniqueAnnotation
4-
class UniqueClass
5-
enum UniqueEnum
6-
fun uniqueFunction()
7-
pipeline uniquePipeline {}
8-
schema UniqueSchema {}
9-
segment uniquePublicSegment() {}
10-
internal segment uniqueInternalSegment() {}
11-
private segment uniquePrivateSegment() {}
3+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
4+
annotation »UniqueAnnotation«
5+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
6+
class »UniqueClass«
7+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
8+
enum »UniqueEnum«
9+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
10+
fun »uniqueFunction«()
11+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
12+
pipeline »uniquePipeline« {}
13+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
14+
schema »UniqueSchema« {}
15+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
16+
segment »uniquePublicSegment«() {}
17+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
18+
internal segment »uniqueInternalSegment«() {}
19+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
20+
private segment »uniquePrivateSegment«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package safeds.lang
2+
3+
/*
4+
* Declarations that only occur a second time in builtin files should be excluded, so we don't get errors while editing them.
5+
*/
6+
7+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
8+
class »Any«
9+
10+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateAnnotation'."
11+
annotation »DuplicateAnnotation«
12+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateClass'."
13+
class »DuplicateClass«
14+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateEnum'."
15+
enum »DuplicateEnum«
16+
// $TEST$ error "Multiple builtin declarations have the name 'duplicateFunction'."
17+
fun »duplicateFunction«()
18+
// $TEST$ no error r"Multiple builtin declarations have the name '\w*'\."
19+
pipeline »duplicatePipeline« {}
20+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateSchema'."
21+
schema »DuplicateSchema« {}
22+
// $TEST$ error "Multiple builtin declarations have the name 'duplicatePublicSegment'."
23+
segment »duplicatePublicSegment«() {}
24+
// $TEST$ error "Multiple builtin declarations have the name 'duplicateInternalSegment'."
25+
internal segment »duplicateInternalSegment«() {}
26+
// $TEST$ no error r"Multiple builtin declarations have the name '\w*'\."
27+
private segment »duplicatePrivateSegment«() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package safeds.lang.other
2+
3+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateAnnotation'."
4+
annotation »DuplicateAnnotation«
5+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateClass'."
6+
class »DuplicateClass«
7+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateEnum'."
8+
enum »DuplicateEnum«
9+
// $TEST$ error "Multiple builtin declarations have the name 'duplicateFunction'."
10+
fun »duplicateFunction«()
11+
// $TEST$ no error r"Multiple builtin declarations have the name '\w*'\."
12+
pipeline »duplicatePipeline« {}
13+
// $TEST$ error "Multiple builtin declarations have the name 'DuplicateSchema'."
14+
schema »DuplicateSchema« {}
15+
// $TEST$ error "Multiple builtin declarations have the name 'duplicatePublicSegment'."
16+
segment »duplicatePublicSegment«() {}
17+
// $TEST$ error "Multiple builtin declarations have the name 'duplicateInternalSegment'."
18+
internal segment »duplicateInternalSegment«() {}
19+
// $TEST$ no error r"Multiple builtin declarations have the name '\w*'\."
20+
private segment »duplicatePrivateSegment«() {}

tests/resources/validation/names/duplicates/across files/safeds.sdstest

-8
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package tests.validation.names.acrossFiles
22

3-
annotation DuplicateAnnotation
4-
class DuplicateClass
5-
enum DuplicateEnum
6-
fun duplicateFunction()
7-
pipeline duplicatePipeline {}
8-
schema DuplicateSchema {}
9-
segment duplicatePublicSegment() {}
10-
internal segment duplicateInternalSegment() {}
11-
private segment duplicatePrivateSegment() {}
3+
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateAnnotation'."
4+
annotation »DuplicateAnnotation«
5+
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateClass'."
6+
class »DuplicateClass«
7+
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateEnum'."
8+
enum »DuplicateEnum«
9+
// $TEST$ error "Multiple declarations in this package have the name 'duplicateFunction'."
10+
fun »duplicateFunction«()
11+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
12+
pipeline »duplicatePipeline« {}
13+
// $TEST$ error "Multiple declarations in this package have the name 'DuplicateSchema'."
14+
schema »DuplicateSchema« {}
15+
// $TEST$ error "Multiple declarations in this package have the name 'duplicatePublicSegment'."
16+
segment »duplicatePublicSegment«() {}
17+
// $TEST$ error "Multiple declarations in this package have the name 'duplicateInternalSegment'."
18+
internal segment »duplicateInternalSegment«() {}
19+
// $TEST$ no error r"Multiple declarations in this package have the name '\w*'\."
20+
private segment »duplicatePrivateSegment«() {}

0 commit comments

Comments
 (0)