Skip to content

Commit

Permalink
feat: const modifier to replace @Constant annotation (#618)
Browse files Browse the repository at this point in the history
Closes #558

### Summary of Changes

Add a `const` modifier for parameters to indicate that they only accept
values that can be evaluated to a constant expression at compile-time.
This replaces the original `@Constant` annotation.

---------

Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 8, 2023
1 parent bf20c7c commit ea4a9ba
Show file tree
Hide file tree
Showing 255 changed files with 2,477 additions and 293 deletions.
14 changes: 7 additions & 7 deletions src/language/formatting/safe-ds-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
isAstNode,
} from 'langium';
import * as ast from '../generated/ast.js';
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js';
import { last } from 'radash';
import noSpace = Formatting.noSpace;
import newLine = Formatting.newLine;
import newLines = Formatting.newLines;
import oneSpace = Formatting.oneSpace;
import indent = Formatting.indent;
import { annotationCallsOrEmpty, literalsOrEmpty, typeArgumentsOrEmpty } from '../helpers/nodeProperties.js';

const newLinesWithIndent = function (count: number, options?: FormattingActionOptions): FormattingAction {
return {
Expand Down Expand Up @@ -549,14 +550,13 @@ export class SafeDsFormatter extends AbstractFormatter {
private formatSdsParameter(node: ast.SdsParameter): void {
const formatter = this.getNodeFormatter(node);

if (annotationCallsOrEmpty(node).length === 0) {
if (node.isVariadic) {
formatter.property('name').prepend(oneSpace());
}
} else {
formatter.property('name').prepend(newLine());
const lastAnnotationCall = last(annotationCallsOrEmpty(node));
if (lastAnnotationCall) {
formatter.node(lastAnnotationCall).append(newLine());
}

formatter.keyword('const').append(oneSpace());
formatter.keyword('vararg').append(oneSpace());
formatter.keyword(':').prepend(noSpace()).append(oneSpace());
formatter.keyword('=').surround(oneSpace());
}
Expand Down
3 changes: 2 additions & 1 deletion src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -416,14 +416,15 @@ SdsParameterList returns SdsParameterList:
;

interface SdsParameter extends SdsLocalVariable {
isConstant: boolean
isVariadic: boolean
^type?: SdsType
defaultValue?: SdsExpression
}

SdsParameter returns SdsParameter:
annotationCalls+=SdsAnnotationCall*
isVariadic?='vararg'?
(isConstant?='const' & isVariadic?='vararg')
name=ID
(':' ^type=SdsType)?
('=' defaultValue=SdsExpression)?
Expand Down
17 changes: 17 additions & 0 deletions src/language/validation/other/declarations/annotations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SdsAnnotation } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { parametersOrEmpty } from '../../../helpers/nodeProperties.js';

export const CODE_ANNOTATION_PARAMETER_CONST_MODIFIER = 'annotation/parameter-const-modifier';

export const annotationParameterShouldNotHaveConstModifier = (node: SdsAnnotation, accept: ValidationAcceptor) => {
for (const parameter of parametersOrEmpty(node)) {
if (parameter.isConstant) {
accept('info', 'Parameters of annotations implicitly have the const modifier.', {
node: parameter,
property: 'name',
code: CODE_ANNOTATION_PARAMETER_CONST_MODIFIER,
});
}
}
};
17 changes: 17 additions & 0 deletions src/language/validation/other/expressions/lambdas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SdsLambda } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { parametersOrEmpty } from '../../../helpers/nodeProperties.js';

export const CODE_LAMBDA_CONST_MODIFIER = 'lambda/const-modifier';

export const lambdaParameterMustNotHaveConstModifier = (node: SdsLambda, accept: ValidationAcceptor): void => {
for (const parameter of parametersOrEmpty(node)) {
if (parameter.isConstant) {
accept('error', 'The const modifier is not applicable to parameters of lambdas.', {
node: parameter,
property: 'name',
code: CODE_LAMBDA_CONST_MODIFIER,
});
}
}
};
16 changes: 16 additions & 0 deletions src/language/validation/other/types/callableTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,24 @@ import { ValidationAcceptor } from 'langium';

import { parametersOrEmpty } from '../../../helpers/nodeProperties.js';

export const CODE_CALLABLE_TYPE_CONST_MODIFIER = 'callable-type/const-modifier';
export const CODE_CALLABLE_TYPE_NO_OPTIONAL_PARAMETERS = 'callable-type/no-optional-parameters';

export const callableTypeParameterMustNotHaveConstModifier = (
node: SdsCallableType,
accept: ValidationAcceptor,
): void => {
for (const parameter of parametersOrEmpty(node)) {
if (parameter.isConstant) {
accept('error', 'The const modifier is not applicable to parameters of callable types.', {
node: parameter,
property: 'name',
code: CODE_CALLABLE_TYPE_CONST_MODIFIER,
});
}
}
};

export const callableTypeMustNotHaveOptionalParameters = (node: SdsCallableType, accept: ValidationAcceptor): void => {
for (const parameter of parametersOrEmpty(node)) {
if (parameter.defaultValue && !parameter.isVariadic) {
Expand Down
20 changes: 17 additions & 3 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import {
parameterListVariadicParameterMustBeLast,
} from './other/declarations/parameterLists.js';
import { unionTypeMustHaveTypeArguments } from './other/types/unionTypes.js';
import { callableTypeMustNotHaveOptionalParameters } from './other/types/callableTypes.js';
import {
callableTypeMustNotHaveOptionalParameters,
callableTypeParameterMustNotHaveConstModifier,
} from './other/types/callableTypes.js';
import { typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/types/typeArgumentLists.js';
import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js';
import { parameterMustNotBeVariadicAndOptional } from './other/declarations/parameters.js';
Expand All @@ -63,6 +66,8 @@ import {
} from './builtins/experimental.js';
import { placeholderShouldBeUsed } from './other/declarations/placeholders.js';
import { segmentParameterShouldBeUsed, segmentResultMustBeAssignedExactlyOnce } from './other/declarations/segments.js';
import { annotationParameterShouldNotHaveConstModifier } from './other/declarations/annotations.js';
import { lambdaParameterMustNotHaveConstModifier } from './other/expressions/lambdas.js';

/**
* Register custom validation checks.
Expand All @@ -75,7 +80,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
assigneeAssignedResultShouldNotBeExperimental(services),
],
SdsAssignment: [assignmentShouldHaveMoreThanWildcardsAsAssignees],
SdsAnnotation: [annotationMustContainUniqueNames, annotationParameterListShouldNotBeEmpty],
SdsAnnotation: [
annotationMustContainUniqueNames,
annotationParameterListShouldNotBeEmpty,
annotationParameterShouldNotHaveConstModifier,
],
SdsAnnotationCall: [
annotationCallAnnotationShouldNotBeDeprecated(services),
annotationCallAnnotationShouldNotBeExperimental(services),
Expand All @@ -89,7 +98,11 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsAttribute: [attributeMustHaveTypeHint],
SdsBlockLambda: [blockLambdaMustContainUniqueNames],
SdsCall: [callArgumentListShouldBeNeeded(services)],
SdsCallableType: [callableTypeMustContainUniqueNames, callableTypeMustNotHaveOptionalParameters],
SdsCallableType: [
callableTypeMustContainUniqueNames,
callableTypeMustNotHaveOptionalParameters,
callableTypeParameterMustNotHaveConstModifier,
],
SdsClass: [classMustContainUniqueNames],
SdsClassBody: [classBodyShouldNotBeEmpty],
SdsConstraintList: [constraintListShouldNotBeEmpty],
Expand All @@ -99,6 +112,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsEnumVariant: [enumVariantMustContainUniqueNames, enumVariantParameterListShouldNotBeEmpty],
SdsExpressionLambda: [expressionLambdaMustContainUniqueNames],
SdsFunction: [functionMustContainUniqueNames, functionResultListShouldNotBeEmpty],
SdsLambda: [lambdaParameterMustNotHaveConstModifier],
SdsMemberAccess: [memberAccessNullSafetyShouldBeNeeded(services)],
SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage],
SdsNamedType: [
Expand Down
4 changes: 0 additions & 4 deletions src/resources/builtins/safeds/lang/coreAnnotations.sdsstub
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,3 @@ annotation Pure
@Description("The function has no side effects.")
@Target(AnnotationTarget.Function)
annotation NoSideEffects

@Description("Values assigned to this parameter must be constant.")
@Target(AnnotationTarget.Parameter)
annotation Constant
7 changes: 7 additions & 0 deletions tests/language/formatting/creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const createFormattingTest = async (uri: URI): Promise<FormattingTest> => {
testName: `[${shortenedResourceName}] should be formatted correctly`,
originalCode,
expectedFormattedCode,
uri,
};
};

Expand All @@ -59,6 +60,7 @@ const invalidTest = (uri: URI, error: Error): FormattingTest => {
testName: `INVALID TEST FILE [${shortenedResourceName}]`,
originalCode: '',
expectedFormattedCode: '',
uri: URI.file(''),
error,
};
};
Expand Down Expand Up @@ -86,6 +88,11 @@ interface FormattingTest extends TestDescription {
* The expected formatted code.
*/
expectedFormattedCode: string;

/**
* The URI of the corresponding file.
*/
uri: URI;
}

/**
Expand Down
7 changes: 7 additions & 0 deletions tests/language/grammar/creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const createGrammarTest = (uri: URI): GrammarTest => {
testName,
code,
expectedResult: comment,
uri,
};
};

Expand All @@ -59,6 +60,7 @@ const invalidTest = (uri: URI, error: Error): GrammarTest => {
testName: `INVALID TEST FILE [${shortenedResourceName}]`,
code: '',
expectedResult: 'invalid',
uri: URI.file(''),
error,
};
};
Expand All @@ -76,6 +78,11 @@ interface GrammarTest extends TestDescription {
* The expected result after parsing the program.
*/
expectedResult: 'syntax_error' | 'no_syntax_error' | 'invalid';

/**
* The URI of the corresponding file.
*/
uri: URI;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/language/grammar/testGrammar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('grammar', () => {
if (test.expectedResult === 'syntax_error') {
if (actualSyntaxErrors.length === 0) {
throw new AssertionError({
message: 'Expected syntax errors but found none.',
message: `Expected syntax errors in ${test.uri} but found none.`,
actual: actualSyntaxErrors,
expected: [],
});
Expand All @@ -37,7 +37,7 @@ describe('grammar', () => {
else if (test.expectedResult === 'no_syntax_error') {
if (actualSyntaxErrors.length > 0) {
throw new AssertionError({
message: 'Expected no syntax errors but found some.',
message: `Expected no syntax errors in ${test.uri} but found some.`,
actual: actualSyntaxErrors,
expected: [],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,73 @@
annotation MyAnnotation (

@Annotation a ,
@Annotation a1 ,

b = 0 ,
b1 = 0 ,

vararg c ,
vararg c1 ,

vararg d : Int = 1 ,
vararg d1 : Int = 1 ,

e : Int ,
e1 : Int ,

f : Int = 2 ,
f1 : Int = 2 ,

vararg g : Int ,
vararg g1 : Int ,

vararg h : Int = 3
vararg h1 : Int = 3 ,

@Annotation const a2 ,

const b2 = 0 ,

const vararg c2 ,

const vararg d2 : Int = 1 ,

const e2 : Int ,

const f2 : Int = 2 ,

const vararg g2 : Int ,

const vararg h2 : Int = 3 ,

@Annotation vararg const a3 ,

vararg const c3 ,

vararg const d3 : Int = 1 ,

vararg const g3 : Int ,

vararg const h3 : Int = 3
)

// -----------------------------------------------------------------------------

annotation MyAnnotation(
@Annotation
a,
b = 0,
vararg c,
vararg d: Int = 1,
e: Int,
f: Int = 2,
vararg g: Int,
vararg h: Int = 3
a1,
b1 = 0,
vararg c1,
vararg d1: Int = 1,
e1: Int,
f1: Int = 2,
vararg g1: Int,
vararg h1: Int = 3,
@Annotation
const a2,
const b2 = 0,
const vararg c2,
const vararg d2: Int = 1,
const e2: Int,
const f2: Int = 2,
const vararg g2: Int,
const vararg h2: Int = 3,
@Annotation
vararg const a3,
vararg const c3,
vararg const d3: Int = 1,
vararg const g3: Int,
vararg const h3: Int = 3
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
annotation MyAnnotation ( const a : Int = 1 )

// -----------------------------------------------------------------------------

annotation MyAnnotation(const a: Int = 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
annotation MyAnnotation ( const a : Int )

// -----------------------------------------------------------------------------

annotation MyAnnotation(const a: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
annotation MyAnnotation1 ( const vararg a : Int = 1 )

annotation MyAnnotation2 ( vararg const a : Int = 1 )

// -----------------------------------------------------------------------------

annotation MyAnnotation1(const vararg a: Int = 1)

annotation MyAnnotation2(vararg const a: Int = 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
annotation MyAnnotation1 ( const vararg a : Int )

annotation MyAnnotation2 ( vararg const a : Int )

// -----------------------------------------------------------------------------

annotation MyAnnotation1(const vararg a: Int)

annotation MyAnnotation2(vararg const a: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
annotation MyAnnotation ( const a = 1 )

// -----------------------------------------------------------------------------

annotation MyAnnotation(const a = 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
annotation MyAnnotation ( const a )

// -----------------------------------------------------------------------------

annotation MyAnnotation(const a)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
annotation MyAnnotation1 ( const vararg a = 1 )

annotation MyAnnotation2 ( vararg const a = 1 )

// -----------------------------------------------------------------------------

annotation MyAnnotation1(const vararg a = 1)

annotation MyAnnotation2(vararg const a = 1)
Loading

0 comments on commit ea4a9ba

Please sign in to comment.