-
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: error if parameter name in impurity reason is invalid (#772)
Closes #741 ### Summary of Changes A `parameterName` in an `ImpurityReason` must be the name of a parameter in the annotated function. This PR adds validation for this.
- Loading branch information
1 parent
87d2a48
commit faa2012
Showing
3 changed files
with
104 additions
and
0 deletions.
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
packages/safe-ds-lang/src/language/validation/builtins/impure.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,72 @@ | ||
import { getContainerOfType, stream, ValidationAcceptor } from 'langium'; | ||
import { isSdsCall, isSdsEnum, isSdsList, SdsFunction } from '../../generated/ast.js'; | ||
import type { SafeDsServices } from '../../safe-ds-module.js'; | ||
import { findFirstAnnotationCallOf, getArguments, getParameters } from '../../helpers/nodeProperties.js'; | ||
import { EvaluatedEnumVariant, StringConstant } from '../../partialEvaluation/model.js'; | ||
|
||
export const CODE_IMPURE_PARAMETER_NAME = 'impure/parameter-name'; | ||
|
||
export const impurityReasonParameterNameMustBelongToParameter = (services: SafeDsServices) => { | ||
const builtinAnnotations = services.builtins.Annotations; | ||
const builtinEnums = services.builtins.Enums; | ||
const nodeMapper = services.helpers.NodeMapper; | ||
const partialEvaluator = services.evaluation.PartialEvaluator; | ||
|
||
return (node: SdsFunction, accept: ValidationAcceptor) => { | ||
const annotationCall = findFirstAnnotationCallOf(node, builtinAnnotations.Impure); | ||
|
||
// Don't further validate if the function is marked as impure and as pure | ||
if (!annotationCall || builtinAnnotations.isPure(node)) { | ||
return; | ||
} | ||
|
||
// Check whether allReasons is valid | ||
const allReasons = nodeMapper.callToParameterValue(annotationCall, 'allReasons'); | ||
if (!isSdsList(allReasons)) { | ||
return; | ||
} | ||
|
||
const parameterNames = stream(getParameters(node)) | ||
.map((it) => it.name) | ||
.toSet(); | ||
|
||
for (const reason of allReasons.elements) { | ||
// If it's not a call, no parameter name could've been provided | ||
if (!isSdsCall(reason)) { | ||
continue; | ||
} | ||
|
||
// Check whether the reason is valid | ||
const evaluatedReason = partialEvaluator.evaluate(reason); | ||
if ( | ||
!(evaluatedReason instanceof EvaluatedEnumVariant) || | ||
getContainerOfType(evaluatedReason.variant, isSdsEnum) !== builtinEnums.ImpurityReason | ||
) { | ||
continue; | ||
} | ||
|
||
// Check whether a parameter name was provided | ||
const parameterName = nodeMapper.callToParameterValue(reason, 'parameterName'); | ||
if (!parameterName) { | ||
continue; | ||
} | ||
|
||
// Check whether parameterName is valid | ||
const evaluatedParameterName = partialEvaluator.evaluate(parameterName); | ||
if (!(evaluatedParameterName instanceof StringConstant)) { | ||
continue; | ||
} | ||
|
||
if (!parameterNames.has(evaluatedParameterName.value)) { | ||
const parameterNameArgument = getArguments(reason).find( | ||
(it) => nodeMapper.argumentToParameter(it)?.name === 'parameterName', | ||
)!; | ||
|
||
accept('error', `The parameter '${evaluatedParameterName.value}' does not exist.`, { | ||
node: parameterNameArgument, | ||
code: CODE_IMPURE_PARAMETER_NAME, | ||
}); | ||
} | ||
} | ||
}; | ||
}; |
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
30 changes: 30 additions & 0 deletions
30
...sources/validation/builtins/annotations/impure/parameter names must be valid/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,30 @@ | ||
package tests.validation.builtins.annotations.impure.parameterNamesMustBeValid | ||
|
||
@Impure([ | ||
// $TEST$ no error r"The parameter '.*' does not exist\." | ||
ImpurityReason.FileReadFromConstantPath(»"text.txt"«), | ||
// $TEST$ no error "The parameter 'p' does not exist." | ||
ImpurityReason.FileReadFromParameterizedPath(»"p"«), | ||
// $TEST$ error "The parameter 'q' does not exist." | ||
ImpurityReason.FileReadFromParameterizedPath(»"q"«), | ||
// $TEST$ no error r"The parameter '.*' does not exist\." | ||
ImpurityReason.FileWriteToConstantPath(»"text.txt"«), | ||
// $TEST$ no error "The parameter 'p' does not exist." | ||
ImpurityReason.FileWriteToParameterizedPath(»"p"«), | ||
// $TEST$ error "The parameter 'q' does not exist." | ||
ImpurityReason.FileWriteToParameterizedPath(»"q"«), | ||
// $TEST$ no error "The parameter 'p' does not exist." | ||
ImpurityReason.PotentiallyImpureParameterCall(»"p"«), | ||
// $TEST$ error "The parameter 'q' does not exist." | ||
ImpurityReason.PotentiallyImpureParameterCall(»"q"«), | ||
|
||
// $TEST$ no error r"The parameter '.*' does not exist\." | ||
ImpurityReason.PotentiallyImpureParameterCall(»1«), | ||
// $TEST$ no error "The parameter 'q' does not exist." | ||
AnotherEnum.SomeVariant(»"q"«) | ||
]) | ||
fun f(p: Int) | ||
|
||
enum AnotherEnum { | ||
SomeVariant(parameterName: String) | ||
} |