Skip to content

Commit

Permalink
Merge pull request #65 from definiti/60-implicit-type
Browse files Browse the repository at this point in the history
60 implicit type
  • Loading branch information
grizio committed Jun 23, 2018
2 parents 5274fca + 79da15c commit 1669a93
Show file tree
Hide file tree
Showing 14 changed files with 354 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/main/antlr/Definiti.g4
Expand Up @@ -105,7 +105,7 @@ definedType :

attributeDefinition:
DOC_COMMENT?
attributeName=IDENTIFIER ':' typeDeclaration verifyingList;
attributeName=IDENTIFIER (':' typeDeclaration)? verifyingList;

typeVerification
: atomicTypeVerification
Expand Down Expand Up @@ -145,7 +145,7 @@ function : ('[' genericTypeList ']')? '(' parameterListDefinition ')' '=>' '{' c
verifyingList : verifying*;
verifying : VERIFYING verificationName=IDENTIFIER ('(' atomicExpressionList ')')?;

parameterDefinition: parameterName=IDENTIFIER ':' typeReference;
parameterDefinition: parameterName=IDENTIFIER (':' typeReference)?;
parameterListDefinition: ((parameterDefinition ',')* parameterDefinition | );

namedFunction: DEF name=IDENTIFIER ('[' genericTypeList ']')? '(' parameterListDefinition ')' ':' genericType '=>' namedFunctionBody;
Expand Down
Expand Up @@ -25,6 +25,10 @@ private[core] trait CommonParser {
ast.Location(file, getRangeFromContext(context))
}

def getLocationFromToken(token: Token): Location = {
ast.Location(file, getRangeFromToken(token))
}

def getRangeFromContext(context: ParserRuleContext): ast.Range = {
val start = Option(context.getStart)
.map(token => Position(token.getLine, token.getCharPositionInLine + 1))
Expand Down
Expand Up @@ -110,9 +110,13 @@ private[core] class DefinitiFileASTParser(
}

private def processAttributeDefinition(context: AttributeDefinitionContext): AttributeDefinition = {
val attributeName = context.attributeName.getText
val typeDeclaration = Option(context.typeDeclaration)
.map(processTypeDeclaration)
.getOrElse(TypeDeclaration(attributeName.capitalize, Seq.empty, Seq.empty, getLocationFromToken(context.attributeName)))
AttributeDefinition(
name = context.attributeName.getText,
typeDeclaration = processTypeDeclaration(context.typeDeclaration),
name = attributeName,
typeDeclaration = typeDeclaration,
comment = Option(context.DOC_COMMENT()).map(_.getText).map(extractDocComment),
verifications = processVerifyingList(context.verifyingList()),
location = getLocationFromContext(context)
Expand Down Expand Up @@ -493,9 +497,13 @@ private[core] class DefinitiFileASTParser(
}

private def processParameter(context: ParameterDefinitionContext): ParameterDefinition = {
val name = context.parameterName.getText
val typeReference = Option(context.typeReference())
.map(processTypeReference)
.getOrElse(TypeReference(identifierWithImport(name.capitalize)))
ParameterDefinition(
name = context.parameterName.getText,
typeReference = processTypeReference(context.typeReference()),
name = name,
typeReference = typeReference,
location = Location(file, getRangeFromContext(context))
)
}
Expand Down
18 changes: 18 additions & 0 deletions src/test/resources/samples/definedType/implicit-attribute-type.def
@@ -0,0 +1,18 @@
type Person {
email verifying IsNonEmptyEmail
address
}

type Email = String

type Address {
city: String
country: String
}

verification IsNonEmptyEmail {
"Please give a non empty email"
(email: Email) => {
email.nonEmpty()
}
}
@@ -0,0 +1,4 @@
type Person {
email
address
}
@@ -0,0 +1,3 @@
def contains[A](list: List[A], element: A): Boolean => {
list.exists((a) => {a == element})
}
3 changes: 3 additions & 0 deletions src/test/resources/samples/namedFunction/implicit-type.def
@@ -0,0 +1,3 @@
def nonEmpty(string): Boolean => {
string.nonEmpty()
}
@@ -0,0 +1,3 @@
def contains[A](list: List[A], element: A): Boolean => {
list.exists((unknown) => {unknown == element})
}
@@ -0,0 +1,3 @@
def nonEmpty(unknown): Boolean => {
unknown.nonEmpty()
}
6 changes: 6 additions & 0 deletions src/test/resources/samples/verification/NonEmptyString.def
@@ -0,0 +1,6 @@
verification NonEmptyString {
"The string should not be empty"
(string) => {
string.nonEmpty()
}
}
6 changes: 6 additions & 0 deletions src/test/resources/samples/verification/NonEmptyUnknown.def
@@ -0,0 +1,6 @@
verification NonEmptyUnknown {
"The unknown should not be empty"
(unknown) => {
unknown.nonEmpty()
}
}
128 changes: 128 additions & 0 deletions src/test/scala/definiti/core/end2end/DefinedTypeSpec.scala
@@ -0,0 +1,128 @@
package definiti.core.end2end

import definiti.common.ast._
import definiti.common.program.{Ko, Ok}
import definiti.common.tests.LocationPath
import definiti.common.utils.ASTUtils._
import definiti.core.ProgramResultMatchers._
import definiti.core.validation.controls.{AttributeTypeControl, TypeDeclarationParametersControl}

class DefinedTypeSpec extends EndToEndSpec {
import DefinedTypeSpec._

"Project.generatePublicAST" should "generate the AST with a defined type with implicit attribute types" in {
val expected = Ok[Root](implicitAttributeType)
val output = processFile("definedType.implicit-attribute-type")
output should beResult(expected)
}

it should "invalid the AST when an implicit type of an attribute does not exist" in {
val expected = Ko[Root](invalidImplicitAttributeTypeErrors)
val output = processFile("definedType.invalid-implicit-attribute-type")
output should beResult(expected)
}
}

object DefinedTypeSpec {
val implicitAttributeTypeLocation = LocationPath("src/test/resources/samples/definedType/implicit-attribute-type.def")
val implicitAttributeType = root(
DefinedType(
name = "Person",
fullName = "Person",
genericTypes = Seq.empty,
parameters = Seq.empty,
attributes = Seq(
AttributeDefinition(
name = "email",
typeDeclaration = TypeDeclaration("Email", Seq.empty, Seq.empty, implicitAttributeTypeLocation(2, 3, 8)),
comment = None,
verifications = Seq(
VerificationReference(
verificationName = "IsNonEmptyEmail",
parameters = Seq.empty,
location = implicitAttributeTypeLocation(2, 9, 34)
)
),
location = implicitAttributeTypeLocation(2, 3, 34)
),
AttributeDefinition(
name = "address",
typeDeclaration = TypeDeclaration("Address", Seq.empty, Seq.empty, implicitAttributeTypeLocation(3, 3, 10)),
comment = None,
verifications = Seq.empty,
location = implicitAttributeTypeLocation(3, 3, 10)
)
),
verifications = Seq.empty,
inherited = Seq.empty,
comment = None,
location = implicitAttributeTypeLocation(1, 1, 4, 2)
),
AliasType(
name = "Email",
fullName = "Email",
genericTypes = Seq.empty,
parameters = Seq.empty,
alias = TypeDeclaration("String", Seq.empty, Seq.empty, implicitAttributeTypeLocation(6, 14, 20)),
inherited = Seq.empty,
verifications = Seq.empty,
comment = None,
location = implicitAttributeTypeLocation(6, 1, 20)
),
DefinedType(
name = "Address",
fullName = "Address",
genericTypes = Seq.empty,
parameters = Seq.empty,
attributes = Seq(
AttributeDefinition(
name = "city",
typeDeclaration = TypeDeclaration("String", Seq.empty, Seq.empty, implicitAttributeTypeLocation(9, 9, 15)),
comment = None,
verifications = Seq.empty,
location = implicitAttributeTypeLocation(9, 3, 15)
),
AttributeDefinition(
name = "country",
typeDeclaration = TypeDeclaration("String", Seq.empty, Seq.empty, implicitAttributeTypeLocation(10, 12, 18)),
comment = None,
verifications = Seq.empty,
location = implicitAttributeTypeLocation(10, 3, 18)
)
),
verifications = Seq.empty,
inherited = Seq.empty,
comment = None,
location = implicitAttributeTypeLocation(8, 1, 11, 2)
),
Verification(
name = "IsNonEmptyEmail",
fullName = "IsNonEmptyEmail",
parameters = Seq.empty,
message = LiteralMessage("Please give a non empty email", implicitAttributeTypeLocation(14, 3, 34)),
function = DefinedFunction(
parameters = Seq(ParameterDefinition("email", TypeReference("Email"), implicitAttributeTypeLocation(15, 4, 16))),
body = MethodCall(
expression = Reference("email", TypeReference("Email"), implicitAttributeTypeLocation(16, 5, 10)),
method = "nonEmpty",
parameters = Seq.empty,
generics = Seq.empty,
returnType = TypeReference("Boolean"),
location = implicitAttributeTypeLocation(16, 5, 21)
),
genericTypes = Seq.empty,
location = implicitAttributeTypeLocation(15, 3, 17, 4)
),
comment = None,
location = implicitAttributeTypeLocation(13, 1, 18, 2)
)
)

val invalidImplicitAttributeTypeErrorsLocation = LocationPath("src/test/resources/samples/definedType/invalid-implicit-attribute-type.def")
val invalidImplicitAttributeTypeErrors = Seq(
AttributeTypeControl.errorUnknownType("Email", invalidImplicitAttributeTypeErrorsLocation(2, 3, 8)),
AttributeTypeControl.errorUnknownType("Address", invalidImplicitAttributeTypeErrorsLocation(3, 3, 10)),
TypeDeclarationParametersControl.errorUnknownType("Email", invalidImplicitAttributeTypeErrorsLocation(2, 3, 8)),
TypeDeclarationParametersControl.errorUnknownType("Address", invalidImplicitAttributeTypeErrorsLocation(3, 3, 10))
)
}
116 changes: 114 additions & 2 deletions src/test/scala/definiti/core/end2end/NamedFunctionSpec.scala
Expand Up @@ -15,16 +15,38 @@ class NamedFunctionSpec extends EndToEndSpec {
output should beResult[Root](expected)
}

"Project.generatePublicAST" should "give error for the invalid named function 'contains' when generics are invalid" in {
it should "give error for the invalid named function 'contains' when generics are invalid" in {
val expected = Ko[Root](invalidContainsGenerics)
val output = processFile("namedFunction.invalid-contains-generics")
output should beResult[Root](expected)
}

"Project.generatePublicAST" should "accept generics in named functions" in {
it should "accept generics in named functions" in {
val output = processFile("namedFunction.nonEmptyList")
output shouldBe ok[Root]
}

it should "define implicitly a type by the parameter name" in {
val expected = Ok(implicitType)
val output = processFile("namedFunction.implicit-type")
output should beResult[Root](expected)
}

it should "refuse a type by its parameter name when the type does not exist" in {
val output = processFile("namedFunction.invalid-implicit-type")
output shouldBe ko[Root]
}

it should "define implicitly a type by the lambda parameter name" in {
val expected = Ok(implicitLambdaParameterType)
val output = processFile("namedFunction.implicit-lambda-parameter-type")
output should beResult[Root](expected)
}

it should "refuse a type by its parameter name when the type does not exist in lambda" in {
val output = processFile("namedFunction.invalid-implicit-lambda-parameter-type")
output shouldBe ko[Root]
}
}

object NamedFunctionSpec {
Expand Down Expand Up @@ -93,4 +115,94 @@ object NamedFunctionSpec {
message = "Class B not found when trying to determine the type of the expression",
location = Location(invalidContainsGenericsSrc, 2, 3, 2, 7)
))

val implicitTypeSrc = "src/test/resources/samples/namedFunction/implicit-type.def"
val implicitType = root(
NamedFunction(
name = "nonEmpty",
fullName = "nonEmpty",
genericTypes = Seq.empty,
parameters = Seq(
ParameterDefinition(
name = "string",
typeReference = TypeReference("String"),
location = Location(implicitTypeSrc, 1, 14, 1, 20)
)
),
returnType = TypeReference("Boolean"),
body = MethodCall(
expression = Reference(
name = "string",
returnType = TypeReference("String"),
location = Location(implicitTypeSrc, 2, 3, 2, 9)
),
method = "nonEmpty",
parameters = Seq.empty,
generics = Seq.empty,
returnType = TypeReference("Boolean"),
location = Location(implicitTypeSrc, 2, 3, 2, 20)
),
location = Location(implicitTypeSrc, 1, 1, 3, 2)
)
)

val implicitLambdaParameterTypeSrc = "src/test/resources/samples/namedFunction/implicit-lambda-parameter-type.def"
val implicitLambdaParameterType = root(
NamedFunction(
name = "contains",
fullName = "contains",
genericTypes = Seq("A"),
parameters = Seq(
ParameterDefinition(
name = "list",
typeReference = TypeReference("List", Seq(TypeReference("A"))),
location = Location(implicitLambdaParameterTypeSrc, 1, 17, 1, 30)
),
ParameterDefinition(
name = "element",
typeReference = TypeReference("A"),
location = Location(implicitLambdaParameterTypeSrc, 1, 32, 1, 42)
)
),
returnType = TypeReference("Boolean"),
body = MethodCall(
expression = Reference(
name = "list",
returnType = TypeReference("List", Seq(TypeReference("A"))),
location = Location(implicitLambdaParameterTypeSrc, 2, 3, 2, 7)
),
method = "exists",
parameters = Seq(
LambdaExpression(
parameterList = Seq(ParameterDefinition(
name = "a",
typeReference = TypeReference("A"),
location = Location(implicitLambdaParameterTypeSrc, 2, 16, 2, 17)
)),
expression = LogicalExpression(
operator = LogicalOperator.Equal,
left = Reference(
name = "a",
returnType = TypeReference("A"),
location = Location(implicitLambdaParameterTypeSrc, 2, 23, 2, 24)
),
right = Reference(
name = "element",
returnType = TypeReference("A"),
location = Location(implicitLambdaParameterTypeSrc, 2, 28, 2, 35)
),
returnType = TypeReference("Boolean"),
location = Location(implicitLambdaParameterTypeSrc, 2, 23, 2, 35)
),
returnType = TypeReference("Boolean"),
location = Location(implicitLambdaParameterTypeSrc, 2, 15, 2, 36)
)
),
generics = Seq.empty,
returnType = TypeReference("Boolean"),
location = Location(implicitLambdaParameterTypeSrc, 2, 3, 2, 37)
),
location = Location(implicitLambdaParameterTypeSrc, 1, 1, 3, 2)
)
)
}

0 comments on commit 1669a93

Please sign in to comment.