-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: various checks for annotations on parameters and results (#625)
Closes partially #543 ### Summary of Changes * Annotations must not be used on parameters of callable types * Annotations must not be used on results of callable types * Annotations must not be used on parameters of lambdas * `@Deprecated` must not be used on required parameters * `@Expert` must not be used on required parameters
- Loading branch information
1 parent
090fcc3
commit e77037e
Showing
12 changed files
with
265 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,44 @@ | ||
import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation } from '../generated/ast.js'; | ||
import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation, SdsParameter } from '../generated/ast.js'; | ||
import { annotationCallsOrEmpty } from '../helpers/nodeProperties.js'; | ||
import { SafeDsModuleMembers } from './safe-ds-module-members.js'; | ||
import { resourceNameToUri } from '../../helpers/resources.js'; | ||
import { URI } from 'langium'; | ||
|
||
const CORE_ANNOTATIONS_URI = resourceNameToUri('builtins/safeds/lang/coreAnnotations.sdsstub'); | ||
|
||
export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> { | ||
isDeprecated(node: SdsAnnotatedObject | undefined): boolean { | ||
return annotationCallsOrEmpty(node).some((it) => { | ||
const annotation = it.annotation?.ref; | ||
return annotation === this.Deprecated; | ||
}); | ||
return this.hasAnnotationCallOf(node, this.Deprecated); | ||
} | ||
|
||
isExperimental(node: SdsAnnotatedObject | undefined): boolean { | ||
return annotationCallsOrEmpty(node).some((it) => { | ||
const annotation = it.annotation?.ref; | ||
return annotation === this.Experimental; | ||
}); | ||
private get Deprecated(): SdsAnnotation | undefined { | ||
return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Deprecated'); | ||
} | ||
|
||
private get Deprecated(): SdsAnnotation | undefined { | ||
return this.getAnnotation('Deprecated'); | ||
isExperimental(node: SdsAnnotatedObject | undefined): boolean { | ||
return this.hasAnnotationCallOf(node, this.Experimental); | ||
} | ||
|
||
private get Experimental(): SdsAnnotation | undefined { | ||
return this.getAnnotation('Experimental'); | ||
return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Experimental'); | ||
} | ||
|
||
isExpert(node: SdsParameter | undefined): boolean { | ||
return this.hasAnnotationCallOf(node, this.Expert); | ||
} | ||
|
||
private get Expert(): SdsAnnotation | undefined { | ||
return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Expert'); | ||
} | ||
|
||
private hasAnnotationCallOf(node: SdsAnnotatedObject | undefined, expected: SdsAnnotation | undefined): boolean { | ||
return annotationCallsOrEmpty(node).some((it) => { | ||
const actual = it.annotation?.ref; | ||
return actual === expected; | ||
}); | ||
} | ||
|
||
private getAnnotation(name: string): SdsAnnotation | undefined { | ||
return this.getModuleMember(CORE_ANNOTATIONS_URI, name, isSdsAnnotation); | ||
private getAnnotation(uri: URI, name: string): SdsAnnotation | undefined { | ||
return this.getModuleMember(uri, name, isSdsAnnotation); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ValidationAcceptor } from 'langium'; | ||
import { SdsParameter } from '../../generated/ast.js'; | ||
import { SafeDsServices } from '../../safe-ds-module.js'; | ||
import { isRequiredParameter } from '../../helpers/nodeProperties.js'; | ||
import { parameterCanBeAnnotated } from '../other/declarations/annotationCalls.js'; | ||
|
||
export const CODE_EXPERT_TARGET_PARAMETER = 'expert/target-parameter'; | ||
|
||
export const requiredParameterMustNotBeExpert = | ||
(services: SafeDsServices) => (node: SdsParameter, accept: ValidationAcceptor) => { | ||
if (isRequiredParameter(node) && parameterCanBeAnnotated(node)) { | ||
if (services.builtins.Annotations.isExpert(node)) { | ||
accept('error', 'An expert parameter must be optional.', { | ||
node, | ||
property: 'name', | ||
code: CODE_EXPERT_TARGET_PARAMETER, | ||
}); | ||
} | ||
} | ||
}; |
51 changes: 51 additions & 0 deletions
51
src/language/validation/other/declarations/annotationCalls.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { | ||
isSdsCallable, | ||
isSdsCallableType, | ||
isSdsLambda, | ||
SdsCallableType, | ||
SdsLambda, | ||
SdsParameter, | ||
} from '../../../generated/ast.js'; | ||
import { getContainerOfType, ValidationAcceptor } from 'langium'; | ||
import { annotationCallsOrEmpty, parametersOrEmpty, resultsOrEmpty } from '../../../helpers/nodeProperties.js'; | ||
|
||
export const CODE_ANNOTATION_CALL_TARGET_PARAMETER = 'annotation-call/target-parameter'; | ||
export const CODE_ANNOTATION_CALL_TARGET_RESULT = 'annotation-call/target-result'; | ||
|
||
export const callableTypeParametersMustNotBeAnnotated = (node: SdsCallableType, accept: ValidationAcceptor) => { | ||
for (const parameter of parametersOrEmpty(node)) { | ||
for (const annotationCall of annotationCallsOrEmpty(parameter)) { | ||
accept('error', 'Parameters of callable types must not be annotated.', { | ||
node: annotationCall, | ||
code: CODE_ANNOTATION_CALL_TARGET_PARAMETER, | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
export const callableTypeResultsMustNotBeAnnotated = (node: SdsCallableType, accept: ValidationAcceptor) => { | ||
for (const result of resultsOrEmpty(node.resultList)) { | ||
for (const annotationCall of annotationCallsOrEmpty(result)) { | ||
accept('error', 'Results of callable types must not be annotated.', { | ||
node: annotationCall, | ||
code: CODE_ANNOTATION_CALL_TARGET_RESULT, | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
export const lambdaParametersMustNotBeAnnotated = (node: SdsLambda, accept: ValidationAcceptor) => { | ||
for (const parameter of parametersOrEmpty(node)) { | ||
for (const annotationCall of annotationCallsOrEmpty(parameter)) { | ||
accept('error', 'Lambda parameters must not be annotated.', { | ||
node: annotationCall, | ||
code: CODE_ANNOTATION_CALL_TARGET_PARAMETER, | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
export const parameterCanBeAnnotated = (node: SdsParameter) => { | ||
const containingCallable = getContainerOfType(node, isSdsCallable); | ||
return !isSdsCallableType(containingCallable) && !isSdsLambda(containingCallable); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
...tion/builtins/annotations/deprecated/must not be used on required parameters/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package tests.validation.builtins.deprecated.mustNotBeUsedOnRequiredParameters | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
annotation MyAnnotation(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
class MyClass(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) { | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
class MyClass(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
fun myFunction(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) | ||
} | ||
|
||
enum MyEnum { | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
MyEnumVariant(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) | ||
} | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
fun myFunction(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) | ||
|
||
// $TEST$ error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
segment mySegment1(@Deprecated »a«: Int, @Deprecated »b«: Int = 3) {} | ||
|
||
// $TEST$ no error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
segment mySegment2( | ||
f: (@Deprecated »a«: Int, @Deprecated »b«: Int = 3) -> () | ||
) { | ||
|
||
// $TEST$ no error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
val g = (@Deprecated »a«: Int, @Deprecated »b«: Int = 3) {}; | ||
|
||
// $TEST$ no error "A deprecated parameter must be optional." | ||
// $TEST$ no error "A deprecated parameter must be optional." | ||
val h = (@Deprecated »a«: Int, @Deprecated »b«: Int = 3) -> 1; | ||
} |
48 changes: 48 additions & 0 deletions
48
...lidation/builtins/annotations/expert/must not be used on required parameters/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package tests.validation.builtins.expert.mustNotBeUsedOnRequiredParameters | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
annotation MyAnnotation(@Expert »a«: Int, @Expert »b«: Int = 3) | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
class MyClass(@Expert »a«: Int, @Expert »b«: Int = 3) { | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
class MyClass(@Expert »a«: Int, @Expert »b«: Int = 3) | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
fun myFunction(@Expert »a«: Int, @Expert »b«: Int = 3) | ||
} | ||
|
||
enum MyEnum { | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
MyEnumVariant(@Expert »a«: Int, @Expert »b«: Int = 3) | ||
} | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
fun myFunction(@Expert »a«: Int, @Expert »b«: Int = 3) | ||
|
||
// $TEST$ error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
segment mySegment1(@Expert »a«: Int, @Expert »b«: Int = 3) {} | ||
|
||
// $TEST$ no error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
segment mySegment2( | ||
f: (@Expert »a«: Int, @Expert »b«: Int = 3) -> () | ||
) { | ||
|
||
// $TEST$ no error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
val g = (@Expert »a«: Int, @Expert »b«: Int = 3) {}; | ||
|
||
// $TEST$ no error "An expert parameter must be optional." | ||
// $TEST$ no error "An expert parameter must be optional." | ||
val h = (@Expert »a«: Int, @Expert »b«: Int = 3) -> 1; | ||
} |
16 changes: 16 additions & 0 deletions
16
...on/other/declarations/annotation calls/must not be used on lambda parameters/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package tests.validation.other.declarations.annotationCalls.mustNotBeUsedOnLambdaParameters | ||
|
||
annotation MyAnnotation | ||
|
||
pipeline myPipeline { | ||
|
||
// $TEST$ error "Lambda parameters must not be annotated." | ||
// $TEST$ error "Lambda parameters must not be annotated." | ||
// $TEST$ error "Lambda parameters must not be annotated." | ||
val f = (»@MyAnnotation« »@MyAnnotation« a: Int, »@MyAnnotation« b: Int = 3) {}; | ||
|
||
// $TEST$ error "Lambda parameters must not be annotated." | ||
// $TEST$ error "Lambda parameters must not be annotated." | ||
// $TEST$ error "Lambda parameters must not be annotated." | ||
val g = (»@MyAnnotation« »@MyAnnotation« a: Int, »@MyAnnotation« b: Int = 3) -> 1; | ||
} |
10 changes: 10 additions & 0 deletions
10
...clarations/annotation calls/must not be used on parameters of callable types/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package tests.validation.other.declarations.annotationCalls.mustNotBeUsedOnParametersOfCallableTypes | ||
|
||
annotation MyAnnotation | ||
|
||
// $TEST$ error "Parameters of callable types must not be annotated." | ||
// $TEST$ error "Parameters of callable types must not be annotated." | ||
// $TEST$ error "Parameters of callable types must not be annotated." | ||
segment mySegment( | ||
f: (»@MyAnnotation« »@MyAnnotation« a: Int, »@MyAnnotation« b: Int = 3) -> () | ||
) {} |
10 changes: 10 additions & 0 deletions
10
.../declarations/annotation calls/must not be used on results of callable types/main.sdstest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package tests.validation.other.declarations.annotationCalls.mustNotBeUsedOnResultsOfCallableTypes | ||
|
||
annotation MyAnnotation | ||
|
||
// $TEST$ error "Results of callable types must not be annotated." | ||
// $TEST$ error "Results of callable types must not be annotated." | ||
// $TEST$ error "Results of callable types must not be annotated." | ||
segment mySegment( | ||
f: () -> (»@MyAnnotation« »@MyAnnotation« a: Int, »@MyAnnotation« b: Int) | ||
) {} |