package org.shedlang.compiler.parser; import org.junit.jupiter.api.Test; import org.shedlang.compiler.parser.errors.InconsistentStructFieldsError; import org.shedlang.compiler.types.Visibility; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.shedlang.compiler.ast.untyped.UntypedNodeMatchers.*; import static org.shedlang.compiler.parser.ParserTesting.parseString; import static org.zwobble.precisely.AssertThat.assertThat; import static org.zwobble.precisely.Matchers.*; public class ParserStructDefinitionTests { @Test public void canParseStructWithNoFields() { var source = """ struct X() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withName("X") .withFields(isSequence()) ); } @Test public void canParseStructWithNonPositionalFields() { var source = """ struct X(.name: String, .accessLevel: AccessLevel) {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withName("X") .withFields(isSequence( isUntypedStructFieldDefinitionNode() .withName("name") .withType(isUntypedTypeLevelReferenceNode("String")), isUntypedStructFieldDefinitionNode() .withName("accessLevel") .withType(isUntypedTypeLevelReferenceNode("AccessLevel")) )) .withFieldsArePositional(false) ); } @Test public void canParseStructWithPositionalFields() { var source = """ struct X(name: String, accessLevel: AccessLevel) {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withName("X") .withFields(isSequence( isUntypedStructFieldDefinitionNode() .withName("name") .withType(isUntypedTypeLevelReferenceNode("String")), isUntypedStructFieldDefinitionNode() .withName("accessLevel") .withType(isUntypedTypeLevelReferenceNode("AccessLevel")) )) .withFieldsArePositional(true) ); } @Test public void mixOfPositionalAndNonPositionalFieldsIsNotAllowed() { var source = """ struct X(name: String, .accessLevel: AccessLevel) {} """; assertThrows( InconsistentStructFieldsError.class, () -> parseString(source, Parser::parseNamespaceStatement) ); } @Test public void defaultVisibilityIsPrivate() { var source = "struct X() {}"; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode().withVisibility(Visibility.PRIVATE) ); } @Test public void whenHasPubPrefixThenVisibilityIsPublic() { var source = "pub struct X() {}"; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode().withVisibility(Visibility.PUBLIC) ); } @Test public void canParseStructWithNoTypeLevelParams() { var source = """ struct X() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withTypeLevelParams(isOptionalEmpty()) ); } @Test public void canParseStructWithSingleTypeLevelParam() { var source = """ struct X[T1]() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withTypeLevelParams(isOptionalOf(isSequence( isUntypedTypeParamNode("T1") ))) ); } @Test public void canParseStructWithMultipleTypeLevelParams() { var source = """ struct X[T1, T2, T3]() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withTypeLevelParams(isOptionalOf(isSequence( isUntypedTypeParamNode("T1"), isUntypedTypeParamNode("T2"), isUntypedTypeParamNode("T3") ))) ); } @Test public void typeLevelParamsCanHaveTrailingComma() { var source = """ struct X[T1,]() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withTypeLevelParams(isOptionalOf(isSequence( isUntypedTypeParamNode("T1") ))) ); } @Test public void canParseStructWithNoMethods() { var source = """ struct X() {} """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withMethods(isSequence()) ); } @Test public void canParseExtendInlineWithSingleMethod() { var source = """ struct X() { fun a(this) { } } """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withMethods(isSequence( isUntypedFunctionDefinitionNode().withName("a") )) ); } @Test public void canParseExtendInlineWithMultipleMethods() { var source = """ struct X() { fun a(this) { } fun b(this) { } fun c(this) { } } """; var node = parseString(source, Parser::parseNamespaceStatement); assertThat( node, isUntypedStructDefinitionNode() .withMethods(isSequence( isUntypedFunctionDefinitionNode().withName("a"), isUntypedFunctionDefinitionNode().withName("b"), isUntypedFunctionDefinitionNode().withName("c") )) ); } }