// buildMapExpression builds the CodeDOM for a map expression. func (db *domBuilder) buildMapExpression(node compilergraph.GraphNode) codedom.Expression { mapScope, _ := db.scopegraph.GetScope(node) mapType := mapScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) eit := node.StartQuery(). Out(parser.NodeMapExpressionChildEntry). BuildNodeIterator() var keyExprs = make([]codedom.Expression, 0) var valueExprs = make([]codedom.Expression, 0) for eit.Next() { entryNode := eit.Node() keyExprs = append(keyExprs, db.getExpression(entryNode, parser.NodeMapExpressionEntryKey)) valueExprs = append(valueExprs, db.getExpression(entryNode, parser.NodeMapExpressionEntryValue)) } if len(valueExprs) == 0 { // Empty map. Call the new() constructor directly. constructor, _ := mapType.ResolveMember("new", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mapType, node), constructor, node), constructor, []codedom.Expression{}, node) } constructor, _ := mapType.ResolveMember("forArrays", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mapType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(keyExprs, node), codedom.ArrayLiteral(valueExprs, node)}, node) }
func (db *domBuilder) buildLambdaExpressionInternal(node compilergraph.GraphNode, paramPredicate compilergraph.Predicate, body codedom.StatementOrExpression, isGenerator bool) codedom.Expression { // Collect the generic names and parameter names of the lambda expression. var generics = make([]string, 0) var parameters = make([]string, 0) git := node.StartQuery(). Out(parser.NodePredicateTypeMemberGeneric). BuildNodeIterator(parser.NodeGenericPredicateName) for git.Next() { generics = append(generics, git.GetPredicate(parser.NodeGenericPredicateName).String()) } pit := node.StartQuery(). Out(paramPredicate). BuildNodeIterator(parser.NodeLambdaExpressionParameterName) for pit.Next() { parameters = append(parameters, pit.GetPredicate(parser.NodeLambdaExpressionParameterName).String()) } // Check for a generator. specialization := codedom.NormalFunction if isGenerator { specialization = codedom.GeneratorFunction } return codedom.FunctionDefinition(generics, parameters, body, false, specialization, node) }
// scopeInlineLambaExpression scopes an inline lambda expression node in the SRG. func (sb *scopeBuilder) scopeInlineLambaExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var returnType = sb.sg.tdg.AnyTypeReference() // Scope the lambda's internal expression. exprScope := sb.getScope(node.GetNode(parser.NodeLambdaExpressionChildExpr), context) if exprScope.GetIsValid() { returnType = exprScope.ResolvedTypeRef(sb.sg.tdg) } // Build the function type. var functionType = sb.sg.tdg.FunctionTypeReference(returnType) // Add the parameter types. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionInferredParameter). BuildNodeIterator() for pit.Next() { parameterType, hasParameterType := sb.inferredParameterTypes.Get(string(pit.Node().NodeId)) if hasParameterType { functionType = functionType.WithParameter(parameterType.(typegraph.TypeReference)) } else { functionType = functionType.WithParameter(sb.sg.tdg.AnyTypeReference()) } } return newScope().IsValid(exprScope.GetIsValid()).Resolving(functionType).GetScope() }
// scopeListLiteralExpression scopes a list literal expression in the SRG. func (sb *scopeBuilder) scopeListLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true var valueType = sb.sg.tdg.VoidTypeReference() // Scope each of the expressions and determine the list type based on its contents. vit := node.StartQuery(). Out(parser.NodeListExpressionValue). BuildNodeIterator() for vit.Next() { valueNode := vit.Node() valueScope := sb.getScope(valueNode, context) if !valueScope.GetIsValid() { isValid = false } else { valueType = valueType.Intersect(valueScope.ResolvedTypeRef(sb.sg.tdg)) } } if valueType.IsVoid() { valueType = sb.sg.tdg.AnyTypeReference() } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.ListTypeReference(valueType)).GetScope() }
// scopeSliceLiteralExpression scopes a slice literal expression in the SRG. func (sb *scopeBuilder) scopeSliceLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true declaredTypeNode := node.GetNode(parser.NodeSliceLiteralExpressionType) declaredType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(declaredTypeNode)) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().Resolving(sb.sg.tdg.SliceTypeReference(sb.sg.tdg.AnyTypeReference())).GetScope() } // Scope each of the expressions and ensure they match the slice type. vit := node.StartQuery(). Out(parser.NodeSliceLiteralExpressionValue). BuildNodeIterator() for vit.Next() { valueNode := vit.Node() valueScope := sb.getScope(valueNode, context) if !valueScope.GetIsValid() { isValid = false } else { if serr := valueScope.ResolvedTypeRef(sb.sg.tdg).CheckSubTypeOf(declaredType); serr != nil { isValid = false sb.decorateWithError(node, "Invalid slice literal value: %v", serr) } } } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.SliceTypeReference(declaredType)).GetScope() }
// buildMappingLiteralExpression builds the CodeDOM for a mapping literal expression. func (db *domBuilder) buildMappingLiteralExpression(node compilergraph.GraphNode) codedom.Expression { mappingScope, _ := db.scopegraph.GetScope(node) mappingType := mappingScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) eit := node.StartQuery(). Out(parser.NodeMappingLiteralExpressionEntryRef). BuildNodeIterator() var entries = make([]codedom.ObjectLiteralEntryNode, 0) for eit.Next() { entryNode := eit.Node() // The key expression must be a string when produced. We either reference it directly (if a string) // or call .String() (if a Stringable). keyNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryKey) keyScope, _ := db.scopegraph.GetScope(keyNode) keyType := keyScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) var keyExpr = db.buildExpression(keyNode) if !keyType.HasReferredType(db.scopegraph.TypeGraph().StringType()) { stringMethod, _ := keyType.ResolveMember("String", typegraph.MemberResolutionInstance) keyExpr = codedom.MemberCall( codedom.MemberReference(db.buildExpression(keyNode), stringMethod, node), stringMethod, []codedom.Expression{}, keyNode) } // Get the expression for the value. valueExpr := db.getExpression(entryNode, parser.NodeMappingLiteralExpressionEntryValue) // Build an object literal expression with the (native version of the) key string and the // created value. entryExpr := codedom.ObjectLiteralEntryNode{ codedom.NominalUnwrapping(keyExpr, db.scopegraph.TypeGraph().StringTypeReference(), keyNode), valueExpr, entryNode, } entries = append(entries, entryExpr) } if len(entries) == 0 { // Empty mapping. Call the Empty() constructor directly. constructor, _ := mappingType.ResolveMember("Empty", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{}, node) } constructor, _ := mappingType.ResolveMember("overObject", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{codedom.ObjectLiteral(entries, node)}, node) }
// buildStructuralNewExpression builds the CodeDOM for a structural new expression. func (db *domBuilder) buildStructuralNewExpression(node compilergraph.GraphNode) codedom.Expression { // Collect the full set of initializers, by member. initializers := map[string]codedom.Expression{} eit := node.StartQuery(). Out(parser.NodeStructuralNewExpressionChildEntry). BuildNodeIterator() for eit.Next() { entryScope, _ := db.scopegraph.GetScope(eit.Node()) entryName, _ := db.scopegraph.GetReferencedName(entryScope) entryMember, _ := entryName.Member() initializers[entryMember.Name()] = db.getExpression(eit.Node(), parser.NodeStructuralNewEntryValue) } childScope, _ := db.scopegraph.GetScope(node.GetNode(parser.NodeStructuralNewTypeExpression)) nodeScope, _ := db.scopegraph.GetScope(node) if nodeScope.HasLabel(proto.ScopeLabel_STRUCTURAL_UPDATE_EXPR) { // Build a call to the Clone() method followed by assignments. resolvedTypeRef := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) return db.buildStructCloneExpression(resolvedTypeRef, initializers, node) } else { // Build a call to the new() constructor of the type with the required field expressions. staticTypeRef := childScope.StaticTypeRef(db.scopegraph.TypeGraph()) return db.buildStructInitializerExpression(staticTypeRef, initializers, node) } }
// buildCollectionLiteralExpression builds a literal collection expression. func (db *domBuilder) buildCollectionLiteralExpression(node compilergraph.GraphNode, valuePredicate compilergraph.Predicate, emptyConstructorName string, arrayConstructorName string) codedom.Expression { collectionScope, _ := db.scopegraph.GetScope(node) collectionType := collectionScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) vit := node.StartQuery(). Out(valuePredicate). BuildNodeIterator() valueExprs := db.buildExpressions(vit, buildExprNormally) return db.buildCollectionInitializerExpression(collectionType, valueExprs, emptyConstructorName, arrayConstructorName, node) }
// buildTemplateStringCall builds the CodeDOM representing the call to a template string function. func (db *domBuilder) buildTemplateStringCall(node compilergraph.GraphNode, funcExpr codedom.Expression, isTagged bool) codedom.Expression { pit := node.StartQuery(). Out(parser.NodeTemplateStringPiece). BuildNodeIterator() var pieceExprs = make([]codedom.Expression, 0) var valueExprs = make([]codedom.Expression, 0) var isPiece = true for pit.Next() { if isPiece { pieceExprs = append(pieceExprs, db.buildExpression(pit.Node())) } else { valueExprs = append(valueExprs, db.buildExpression(pit.Node())) } isPiece = !isPiece } // Handle common case: No literal string piece at all. if len(pieceExprs) == 0 { return codedom.NominalWrapping(codedom.LiteralValue("''", node), db.scopegraph.TypeGraph().StringType(), node) } // Handle common case: A single literal string piece with no values. if len(pieceExprs) == 1 && len(valueExprs) == 0 { return pieceExprs[0] } pieceSliceType := db.scopegraph.TypeGraph().SliceTypeReference(db.scopegraph.TypeGraph().StringTypeReference()) valueSliceType := db.scopegraph.TypeGraph().SliceTypeReference(db.scopegraph.TypeGraph().StringableTypeReference()) constructor, _ := pieceSliceType.ResolveMember("overArray", typegraph.MemberResolutionStatic) pieceSliceExpr := codedom.MemberCall( codedom.MemberReference( codedom.TypeLiteral(pieceSliceType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(pieceExprs, node)}, node) valueSliceExpr := codedom.MemberCall( codedom.MemberReference( codedom.TypeLiteral(valueSliceType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(valueExprs, node)}, node) return codedom.AwaitPromise(codedom.FunctionCall(funcExpr, []codedom.Expression{pieceSliceExpr, valueSliceExpr}, node), node) }
// scopeTypeConversionExpression scopes a conversion from a nominal type to a base type. func (sb *scopeBuilder) scopeTypeConversionExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { childScope := sb.getScope(node.GetNode(parser.NodeFunctionCallExpressionChildExpr), context) conversionType := childScope.StaticTypeRef(sb.sg.tdg) // Ensure that the function call has a single argument. ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() var index = 0 var isValid = true for ait.Next() { if index > 0 { sb.decorateWithError(node, "Type conversion requires a single argument") isValid = false break } index = index + 1 // Make sure the argument's scope is valid. argumentScope := sb.getScope(ait.Node(), context) if !argumentScope.GetIsValid() { isValid = false continue } // The argument must be a nominal subtype of the conversion type. argumentType := argumentScope.ResolvedTypeRef(sb.sg.tdg) if nerr := argumentType.CheckNominalConvertable(conversionType); nerr != nil { sb.decorateWithError(node, "Cannot perform type conversion: %v", nerr) isValid = false break } if argumentType.IsNullable() { conversionType = conversionType.AsNullable() } } if index == 0 { sb.decorateWithError(node, "Type conversion requires a single argument") isValid = false } // The type conversion returns an instance of the converted type. return newScope().IsValid(isValid).Resolving(conversionType).GetScope() }
// buildGenericSpecifierExpression builds the CodeDOM for a generic specification of a function or type. func (db *domBuilder) buildGenericSpecifierExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeGenericSpecifierChildExpr) // Collect the generic types being specified. git := node.StartQuery(). Out(parser.NodeGenericSpecifierType). BuildNodeIterator() var genericTypes = make([]codedom.Expression, 0) for git.Next() { replacementType, _ := db.scopegraph.ResolveSRGTypeRef(db.scopegraph.SourceGraph().GetTypeRef(git.Node())) genericTypes = append(genericTypes, codedom.TypeLiteral(replacementType, git.Node())) } return codedom.FunctionCall(childExpr, genericTypes, node) }
// scopeMappingLiteralExpression scopes a mapping literal expression in the SRG. func (sb *scopeBuilder) scopeMappingLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true declaredTypeNode := node.GetNode(parser.NodeMappingLiteralExpressionType) declaredType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(declaredTypeNode)) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().Resolving(sb.sg.tdg.MappingTypeReference(sb.sg.tdg.AnyTypeReference())).GetScope() } // Scope each of the entries and ensure they match the mapping value type. eit := node.StartQuery(). Out(parser.NodeMappingLiteralExpressionEntryRef). BuildNodeIterator() for eit.Next() { entryNode := eit.Node() keyNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryKey) valueNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryValue) keyScope := sb.getScope(keyNode, context) valueScope := sb.getScope(valueNode, context) if keyScope.GetIsValid() { localKeyType := keyScope.ResolvedTypeRef(sb.sg.tdg) if serr := localKeyType.CheckSubTypeOf(sb.sg.tdg.StringableTypeReference()); serr != nil { sb.decorateWithError(keyNode, "Mapping literal keys must be of type Stringable: %v", serr) isValid = false } } else { isValid = false } if valueScope.GetIsValid() { localValueType := valueScope.ResolvedTypeRef(sb.sg.tdg) if serr := localValueType.CheckSubTypeOf(declaredType); serr != nil { sb.decorateWithError(keyNode, "Expected mapping values of type %v: %v", declaredType, serr) isValid = false } } else { isValid = false } } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.MappingTypeReference(declaredType)).GetScope() }
// buildFunctionCall builds the CodeDOM for a function call. func (db *domBuilder) buildFunctionCall(node compilergraph.GraphNode) codedom.Expression { childExprNode := node.GetNode(parser.NodeFunctionCallExpressionChildExpr) childScope, _ := db.scopegraph.GetScope(childExprNode) // Check if the child expression has a static scope. If so, this is a type conversion between // a nominal type and a base type. if childScope.GetKind() == proto.ScopeKind_STATIC { wrappedExprNode := node.GetNode(parser.NodeFunctionCallArgument) wrappedExprScope, _ := db.scopegraph.GetScope(wrappedExprNode) wrappedExprType := wrappedExprScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) wrappedExpr := db.buildExpression(wrappedExprNode) targetTypeRef := childScope.StaticTypeRef(db.scopegraph.TypeGraph()) // If the targetTypeRef is not nominal or structural, then we know we are unwrapping. if !targetTypeRef.IsNominalOrStruct() { return codedom.NominalUnwrapping(wrappedExpr, wrappedExprType, node) } else { return codedom.NominalRefWrapping(wrappedExpr, wrappedExprType, targetTypeRef, node) } } // Collect the expressions for the arguments. ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() arguments := db.buildExpressions(ait, buildExprCheckNominalShortcutting) childExpr := db.buildExpression(childExprNode) // If the function call is to a member, then we return a MemberCall. namedRef, isNamed := db.scopegraph.GetReferencedName(childScope) if isNamed && !namedRef.IsLocal() { member, _ := namedRef.Member() if childExprNode.Kind() == parser.NodeNullableMemberAccessExpression { return codedom.NullableMemberCall(childExpr, member, arguments, node) } return codedom.MemberCall(childExpr, member, arguments, node) } // Otherwise, this is a normal function call with an await. return codedom.AwaitPromise(codedom.FunctionCall(childExpr, arguments, node), node) }
// scopeMapLiteralExpression scopes a map literal expression in the SRG. func (sb *scopeBuilder) scopeMapLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true var keyType = sb.sg.tdg.VoidTypeReference() var valueType = sb.sg.tdg.VoidTypeReference() // Scope each of the entries and determine the map key and value types based on the entries found. eit := node.StartQuery(). Out(parser.NodeMapExpressionChildEntry). BuildNodeIterator() for eit.Next() { entryNode := eit.Node() keyNode := entryNode.GetNode(parser.NodeMapExpressionEntryKey) valueNode := entryNode.GetNode(parser.NodeMapExpressionEntryValue) keyScope := sb.getScope(keyNode, context) valueScope := sb.getScope(valueNode, context) if !keyScope.GetIsValid() || !valueScope.GetIsValid() { isValid = false continue } localKeyType := keyScope.ResolvedTypeRef(sb.sg.tdg) localValueType := valueScope.ResolvedTypeRef(sb.sg.tdg) if serr := localKeyType.CheckSubTypeOf(sb.sg.tdg.MappableTypeReference()); serr != nil { sb.decorateWithError(keyNode, "Map literal keys must be of type Mappable: %v", serr) isValid = false } keyType = keyType.Intersect(localKeyType) valueType = valueType.Intersect(localValueType) } if keyType.IsVoid() || keyType.IsAny() { keyType = sb.sg.tdg.MappableTypeReference() } if valueType.IsVoid() { valueType = sb.sg.tdg.AnyTypeReference() } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.MapTypeReference(keyType, valueType)).GetScope() }
// getDeclaredVariableType returns the declared type of a variable statement, member or type field (if any). func (sb *scopeBuilder) getDeclaredVariableType(node compilergraph.GraphNode) (typegraph.TypeReference, bool) { declaredTypeNode, hasDeclaredType := node.StartQuery(). Out(parser.NodeVariableStatementDeclaredType, parser.NodePredicateTypeMemberDeclaredType). TryGetNode() if !hasDeclaredType { return sb.sg.tdg.AnyTypeReference(), false } // Load the declared type. declaredType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(declaredTypeNode)) if rerr != nil { panic(rerr) return sb.sg.tdg.AnyTypeReference(), false } return declaredType, true }
// getFunctionCallArgumentTypes returns the resolved types of the argument expressions to the given function // call. func (sb *scopeBuilder) getFunctionCallArgumentTypes(node compilergraph.GraphNode, context scopeContext) []typegraph.TypeReference { ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() var types = make([]typegraph.TypeReference, 0) for ait.Next() { // Resolve the scope of the argument. argumentScope := sb.getScope(ait.Node(), context) if !argumentScope.GetIsValid() { continue } types = append(types, argumentScope.ResolvedTypeRef(sb.sg.tdg)) } return types }
// scopeFullLambaExpression scopes a fully defined lambda expression node in the SRG. func (sb *scopeBuilder) scopeFullLambaExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var returnType = sb.sg.tdg.AnyTypeReference() // Check for a defined return type for the lambda expression. returnTypeNode, hasReturnType := node.TryGetNode(parser.NodeLambdaExpressionReturnType) if hasReturnType { resolvedReturnType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(returnTypeNode)) if rerr != nil { panic(rerr) } returnType = resolvedReturnType } // Scope the block. If the function has no defined return type, we use the return type of the block. blockScope := sb.getScope(node.GetNode(parser.NodeLambdaExpressionBlock), context.withImplemented(node)) if !hasReturnType && blockScope.GetIsValid() { returnType = blockScope.ReturnedTypeRef(sb.sg.tdg) } // Build the function type. var functionType = sb.sg.tdg.FunctionTypeReference(returnType) // Add the parameter types. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionParameter). BuildNodeIterator() for pit.Next() { parameterTypeNode := pit.Node().GetNode(parser.NodeParameterType) parameterType, perr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(parameterTypeNode)) if perr != nil { panic(perr) } functionType = functionType.WithParameter(parameterType) } return newScope().IsValid(blockScope.GetIsValid()).Resolving(functionType).GetScope() }
// adjustedMemberSignature returns the member signature found on the given node, adjusted for // the parent type's generics, as specified in this type reference. Will panic if the type reference // does not refer to the node's parent type. func (tr TypeReference) adjustedMemberSignature(node compilergraph.GraphNode) string { compilerutil.DCHECK(func() bool { return node.StartQuery().In(NodePredicateMember).GetNode() == tr.referredTypeNode() }, "Type reference must be parent of member node") // Retrieve the generics of the parent type. parentNode := tr.referredTypeNode() pgit := parentNode.StartQuery().Out(NodePredicateTypeGeneric).BuildNodeIterator() // Parse the member signature. esig := &proto.MemberSig{} memberSig := node.GetTagged(NodePredicateMemberSignature, esig).(*proto.MemberSig) // Replace the generics of the parent type in the signature with those of the type reference. generics := tr.Generics() var index = 0 var memberType = tr.Build(memberSig.GetMemberType()).(TypeReference) for pgit.Next() { genericNode := pgit.Node() genericRef := generics[index] genericType := TGTypeDecl{genericNode, tr.tdg} // Replace the generic in the member type. memberType = memberType.ReplaceType(genericType, genericRef) // Replace the generic in any generic constraints. for cindex, constraint := range memberSig.GetGenericConstraints() { memberSig.GenericConstraints[cindex] = tr.Build(constraint).(TypeReference). ReplaceType(genericType, genericRef). Value() } index = index + 1 } adjustedType := memberType.Value() memberSig.MemberType = &adjustedType return memberSig.Value() }
// buildStatementBlock builds the CodeDOM for a statement block. func (db *domBuilder) buildStatementBlock(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { sit := node.StartQuery(). Out(parser.NodeStatementBlockStatement). BuildNodeIterator() startStatement := codedom.EmptyStatement(node) var current codedom.Statement = startStatement for sit.Next() { startStatement, endStatement := db.buildStatements(sit.Node()) codedom.AssignNextStatement(current, startStatement) current = endStatement // If the current node is a terminating statement, skip the rest of the block. scope, hasScope := db.scopegraph.GetScope(sit.Node()) if hasScope && scope.GetIsTerminatingStatement() { break } } return startStatement, current }
// scopeStructuralNewEntries scopes all the entries of a structural new expression. func (sb *scopeBuilder) scopeStructuralNewEntries(node compilergraph.GraphNode, context scopeContext) (map[string]bool, bool) { // Scope the defined entries. We also build a list here to ensure all required entries are // added. encountered := map[string]bool{} eit := node.StartQuery(). Out(parser.NodeStructuralNewExpressionChildEntry). BuildNodeIterator(parser.NodeStructuralNewEntryKey) var isValid = true for eit.Next() { // Scope the entry. entryName := eit.GetPredicate(parser.NodeStructuralNewEntryKey).String() entryScope := sb.getScope(eit.Node(), context) if !entryScope.GetIsValid() { isValid = false } encountered[entryName] = true } return encountered, isValid }
// scopeTemplateStringExpression scopes a template string expression in the SRG. func (sb *scopeBuilder) scopeTemplateStringExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope each of the pieces of the template string. All pieces must be strings or Stringable. pit := node.StartQuery(). Out(parser.NodeTemplateStringPiece). BuildNodeIterator() var isValid = true for pit.Next() { pieceNode := pit.Node() pieceScope := sb.getScope(pieceNode, context) if !pieceScope.GetIsValid() { isValid = false continue } pieceType := pieceScope.ResolvedTypeRef(sb.sg.tdg) if serr := pieceType.CheckSubTypeOf(sb.sg.tdg.StringableTypeReference()); serr != nil { isValid = false sb.decorateWithError(pieceNode, "All expressions in a template string must be of type Stringable: %v", serr) } } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.StringTypeReference()).GetScope() }
// scopeFunctionCallExpression scopes a function call expression in the SRG. func (sb *scopeBuilder) scopeFunctionCallExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the child expression. childExpr := node.GetNode(parser.NodeFunctionCallExpressionChildExpr) childScope := sb.getScope(childExpr, context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } // Check if the child expression has a static scope. If so, this is a type conversion between // a nominal type and a base type. if childScope.GetKind() == proto.ScopeKind_STATIC { return sb.scopeTypeConversionExpression(node, context) } else if childScope.GetKind() == proto.ScopeKind_GENERIC { namedScopedRef, found := sb.getNamedScopeForScope(childScope) if found { sb.decorateWithError(node, "Cannot invoke function call on unclarified generic %s %s.", namedScopedRef.Title(), namedScopedRef.Name()) } else { sb.decorateWithError(node, "Cannot invoke function call on unclarified generic scope.") } return newScope().Invalid().GetScope() } namedNode, hasNamedNode := sb.getNamedScopeForScope(childScope) if hasNamedNode { context.staticDependencyCollector.registerNamedDependency(namedNode) } getDescription := func() string { if !hasNamedNode { return "" } return fmt.Sprintf("on %v %v ", namedNode.Title(), namedNode.Name()) } // Ensure the child expression has type function. childType := childScope.ResolvedTypeRef(sb.sg.tdg) if !childType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { // If the child type is a function, but nullable, only allow it to be called if the type // is a result of a null access expression. This is a special case to allow writing code // such as `foo?.bar()` easier, without allowing for random `someNullableFunc()` to be // called. // // TODO: It might be a good idea to revisit this decision if we find `someNullableFunc()` to // be a useful pattern as well. if !childType.HasReferredType(sb.sg.tdg.FunctionType()) || childExpr.Kind() != parser.NodeNullableMemberAccessExpression { sb.decorateWithError(node, "Cannot invoke function call on non-function '%v'.", childType) return newScope().Invalid().GetScope() } } // Find the starting index of the nullable parameters. Once all parameters are nullable, // they are considered optional. var nonOptionalIndex = -1 childParameters := childType.Parameters() for parameterIndex, parameterType := range childParameters { if !parameterType.NullValueAllowed() { nonOptionalIndex = parameterIndex } } // Ensure that the parameters of the function call match those of the child type. var index = -1 ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() var isValid = true for ait.Next() { index = index + 1 // Resolve the scope of the argument. argumentScope := sb.getScope(ait.Node(), context) if !argumentScope.GetIsValid() { isValid = false nonOptionalIndex = index continue } if index < len(childParameters) { // Ensure the type of the argument matches the parameter. argumentType := argumentScope.ResolvedTypeRef(sb.sg.tdg) serr, exception := argumentType.CheckSubTypeOfWithExceptions(childParameters[index], typegraph.AllowNominalWrappedForData) if serr != nil { sb.decorateWithError(ait.Node(), "Parameter #%v %sexpects type %v: %v", index+1, getDescription(), childParameters[index], serr) isValid = false } // If a nominally-wrapped value was used in place of an argument that expects its data type, then // mark the expression as being a shortcut that needs unwrapping during generation. if exception == typegraph.AllowNominalWrappedForData { sb.decorateWithSecondaryLabel(ait.Node(), proto.ScopeLabel_NOMINALLY_SHORTCUT_EXPR) } } } if index < nonOptionalIndex { sb.decorateWithError(node, "Function call %sexpects %v non-optional arguments, found %v", getDescription(), nonOptionalIndex+1, index+1) return newScope().Invalid().GetScope() } if index >= len(childParameters) { sb.decorateWithError(node, "Function call %sexpects %v arguments, found %v", getDescription(), len(childParameters), index+1) return newScope().Invalid().GetScope() } var returnType = childType.Generics()[0] if childType.IsNullable() { returnType = returnType.AsNullable() } // Check for an awaitable return type. If found and this call is not under an assignment or // arrow, warn. if isValid && returnType.IsDirectReferenceTo(sb.sg.tdg.AwaitableType()) { if !returnType.Generics()[0].IsVoid() { if _, underStatement := node.TryGetIncomingNode(parser.NodeExpressionStatementExpression); underStatement { sb.decorateWithWarning(node, "Returned Awaitable resolves a value of type %v which is not handled", returnType.Generics()[0]) } } } // The function call returns the first generic of the function. return newScope().IsValid(isValid).Resolving(returnType).GetScope() }
// scopeMatchStatement scopes a match statement in the SRG. func (sb *scopeBuilder) scopeMatchStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the match expression. matchExprScope := sb.getScope(node.GetNode(parser.NodeMatchStatementExpression), context) if !matchExprScope.GetIsValid() { return newScope().Invalid().GetScope() } matchExprType := matchExprScope.ResolvedTypeRef(sb.sg.tdg) // Scope each of the case statements under the match, ensuring that their type is a subtype // of the match expression's type. var returnedType = sb.sg.tdg.VoidTypeReference() var settlesScope = true var hasDefault = false var labelSet = newLabelSet() var isValid = true // Lookup the named value, if any. matchNamedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue) sit := node.StartQuery(). Out(parser.NodeMatchStatementCase). BuildNodeIterator() for sit.Next() { var matchBranchType = sb.sg.tdg.AnyTypeReference() // Check the case's type reference (if any) against the type expected. caseTypeRefNode, hasCaseTypeRef := sit.Node().TryGetNode(parser.NodeMatchStatementCaseTypeReference) if hasCaseTypeRef { matchTypeRef, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(caseTypeRefNode)) if rerr != nil { panic(rerr) } // Ensure that the type is not nullable, as then a null could match anything. if matchTypeRef.IsNullable() { sb.decorateWithError(node, "Match cases cannot be nullable. Found: %v", matchTypeRef) isValid = false } else if serr := matchTypeRef.CheckCastableFrom(matchExprType); serr != nil { // Ensure that the type is a subtype of the expression type. sb.decorateWithError(node, "Match cases must be castable from type '%v': %v", matchExprType, serr) isValid = false } else { matchBranchType = matchTypeRef } } else { hasDefault = true } // Build the local context for scoping. If this match has an 'as', then its type is overridden // to the match type for each branch. localContext := context if hasNamedValue { localContext = context.withTypeOverride(matchNamedValue, matchBranchType) } // Scope the statement block under the case. statementBlockNode := sit.Node().GetNode(parser.NodeMatchStatementCaseStatement) statementBlockScope := sb.getScope(statementBlockNode, localContext.withBreakable(node)) if !statementBlockScope.GetIsValid() { isValid = false } labelSet.AppendLabelsOf(statementBlockScope) returnedType = returnedType.Intersect(statementBlockScope.ReturnedTypeRef(sb.sg.tdg)) settlesScope = settlesScope && statementBlockScope.GetIsSettlingScope() } // If there isn't a default case, then the match cannot be known to return in all cases. if !hasDefault { returnedType = sb.sg.tdg.VoidTypeReference() settlesScope = false } return newScope().IsValid(isValid).Returning(returnedType, settlesScope).WithLabelSet(labelSet).GetScope() }
// scopeSwitchStatement scopes a switch statement in the SRG. func (sb *scopeBuilder) scopeSwitchStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Check for an expression. If a switch has an expression, then all cases must match against it. var isValid = true var switchValueType = sb.sg.tdg.BoolTypeReference() exprNode, hasExpression := node.TryGetNode(parser.NodeSwitchStatementExpression) if hasExpression { exprScope := sb.getScope(exprNode, context) if exprScope.GetIsValid() { switchValueType = exprScope.ResolvedTypeRef(sb.sg.tdg) } else { isValid = false } } // Ensure that the switch type has a defined accessible comparison operator. if isValid { module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) _, rerr := switchValueType.ResolveAccessibleMember("equals", module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Cannot switch over instance of type '%v', as it does not define or export an 'equals' operator", switchValueType) isValid = false } } // Scope each of the case statements under the switch. var returnedType = sb.sg.tdg.VoidTypeReference() var settlesScope = true var hasDefault = false var labelSet = newLabelSet() sit := node.StartQuery(). Out(parser.NodeSwitchStatementCase). BuildNodeIterator() for sit.Next() { // Scope the statement block under the case. statementBlockNode := sit.Node().GetNode(parser.NodeSwitchStatementCaseStatement) statementBlockScope := sb.getScope(statementBlockNode, context.withBreakable(node)) if !statementBlockScope.GetIsValid() { isValid = false } labelSet.AppendLabelsOf(statementBlockScope) returnedType = returnedType.Intersect(statementBlockScope.ReturnedTypeRef(sb.sg.tdg)) settlesScope = settlesScope && statementBlockScope.GetIsSettlingScope() // Check the case's expression (if any) against the type expected. caseExprNode, hasCaseExpression := sit.Node().TryGetNode(parser.NodeSwitchStatementCaseExpression) if hasCaseExpression { caseExprScope := sb.getScope(caseExprNode, context) if caseExprScope.GetIsValid() { caseExprType := caseExprScope.ResolvedTypeRef(sb.sg.tdg) if serr := caseExprType.CheckSubTypeOf(switchValueType); serr != nil { sb.decorateWithError(node, "Switch cases must have values matching type '%v': %v", switchValueType, serr) isValid = false } } else { isValid = false } } else { hasDefault = true } } // If there isn't a default case, then the switch cannot be known to return in all cases. if !hasDefault { returnedType = sb.sg.tdg.VoidTypeReference() settlesScope = false } return newScope().IsValid(isValid).Returning(returnedType, settlesScope).WithLabelSet(labelSet).GetScope() }
// scopeStatementBlock scopes a block of statements in the SRG. func (sb *scopeBuilder) scopeStatementBlock(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { sit := node.StartQuery(). Out(parser.NodeStatementBlockStatement). BuildNodeIterator() // Scope all the child statements, collecting the types returned along the way. var returnedType = sb.sg.tdg.VoidTypeReference() var isSettlingScope = false var isValid = true var skipRemaining = false var unreachableWarned = false var labelSet = newLabelSet() for sit.Next() { statementScope := sb.getScope(sit.Node(), context) labelSet.AppendLabelsOf(statementScope) if skipRemaining { if !unreachableWarned { sb.decorateWithWarning(node, "Unreachable statement found") } unreachableWarned = true continue } if !statementScope.GetIsValid() { isValid = false } returnedType = returnedType.Intersect(statementScope.ReturnedTypeRef(sb.sg.tdg)) isSettlingScope = isSettlingScope || statementScope.GetIsSettlingScope() if statementScope.GetIsTerminatingStatement() { skipRemaining = true } } // If this statement block is the implementation of a member or property getter, check its return // type. if isValid { if labelSet.HasLabel(proto.ScopeLabel_GENERATOR_STATEMENT) { if !returnedType.IsVoid() { sb.decorateWithError(node, "Cannot return a type under a generator") return newScope().Invalid().WithLabelSet(labelSet).GetScope() } } else { parentDef, hasParent := node.StartQuery().In(parser.NodePredicateBody).TryGetNode() if hasParent { returnTypeExpected, hasReturnType := sb.sg.tdg.LookupReturnType(parentDef) if hasReturnType { // If the return type expected is void, ensure no branch returned any values. if returnTypeExpected.IsVoid() { if returnedType.IsVoid() { return newScope().Valid().Returning(returnedType, isSettlingScope).WithLabelSet(labelSet).GetScope() } else { sb.decorateWithError(node, "No return value expected here, found value of type '%v'", returnedType) return newScope().Invalid().Returning(returnedType, isSettlingScope).WithLabelSet(labelSet).GetScope() } } if !isSettlingScope { sb.decorateWithError(node, "Expected return value of type '%v' but not all paths return a value", returnTypeExpected) return newScope().Invalid().Returning(returnedType, isSettlingScope).WithLabelSet(labelSet).GetScope() } // Otherwise, check that the returned type matches that expected. if !returnedType.IsVoid() { rerr := returnedType.CheckSubTypeOf(returnTypeExpected) if rerr != nil { sb.decorateWithError(node, "Expected return value of type '%v': %v", returnTypeExpected, rerr) return newScope().Invalid().Returning(returnedType, isSettlingScope).WithLabelSet(labelSet).GetScope() } } } } } } // No valid return type expected. return newScope().IsValid(isValid).Returning(returnedType, isSettlingScope).WithLabelSet(labelSet).GetScope() }
// buildJumpingCaseStatement builds the CodeDOM for a statement which jumps (branches) based on // various cases. func (db *domBuilder) buildJumpingCaseStatement(node compilergraph.GraphNode, casePredicate compilergraph.Predicate, caseStatementPredicate compilergraph.Predicate, caseExpressionPredicate compilergraph.Predicate, startStatement codedom.Statement, getCheckExpression checkExpressionGenerator) (codedom.Statement, codedom.Statement) { // A branching statement is an extended version of a conditional statement. For each branch, we check if the // branch's value matches the conditional (or "true" if there is no conditional expr). On true, the block // under the case is executed, followed by a jump to the final statement. Otherwise, the next block // in the chain is executed. finalStatement := codedom.EmptyStatement(node) // Save a break statement reference for the generated statement. db.breakStatementMap[node.NodeId] = finalStatement // Generate the statements and check expressions for each of the cases. var branchJumps = make([]*codedom.ConditionalJumpNode, 0) cit := node.StartQuery(). Out(casePredicate). BuildNodeIterator() for cit.Next() { caseNode := cit.Node() caseStart, caseEnd := db.getStatements(caseNode, caseStatementPredicate) caseExpressionNode, hasCaseExpression := caseNode.TryGetNode(caseExpressionPredicate) // Generate the expression against which we should check. If there is no expression on // the case, then this is the default and we compare against "true". var branchExpression codedom.Expression = nil if hasCaseExpression { branchExpression = getCheckExpression(caseExpressionNode) } else { branchExpression = codedom.NominalWrapping( codedom.LiteralValue("true", caseNode), db.scopegraph.TypeGraph().BoolType(), caseNode) } branchJump := codedom.BranchOn(branchExpression, caseNode) branchJump.True = caseStart branchJumps = append(branchJumps, branchJump) // Jump the end statement, once complete, to the final statement. codedom.AssignNextStatement(caseEnd, codedom.UnconditionalJump(finalStatement, caseNode)) } // Generate a "trampoline" that checks each case. for index, branchJump := range branchJumps { // Jump the current branch (on false) to the next conditional check. if index < len(branchJumps)-1 { branchJump.False = branchJumps[index+1] } else { branchJump.False = finalStatement } } // Have the start state jump to the first branch (if any). if len(branchJumps) > 0 { codedom.AssignNextStatement(startStatement, branchJumps[0]) } return startStatement, finalStatement }
// inferLambdaParameterTypes performs type inference to determine the types of the parameters of the // given lambda expression (if necessary). // // Forms supported for inference: // // var someVar = (a, b) => someExpr // someVar(1, 2) // // var<function<void>(...)> = (a, b) => someExpr // // ((a, b) => someExpr)(1, 2) func (sb *scopeBuilder) inferLambdaParameterTypes(node compilergraph.GraphNode, context scopeContext) { // If the lambda has no inferred parameters, nothing more to do. if _, ok := node.TryGetNode(parser.NodeLambdaExpressionInferredParameter); !ok { return } // Otherwise, collect the names and positions of the inferred parameters. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionInferredParameter). BuildNodeIterator() var inferenceParameters = make([]compilergraph.GraphNode, 0) for pit.Next() { inferenceParameters = append(inferenceParameters, pit.Node()) } getInferredTypes := func() ([]typegraph.TypeReference, bool) { // Check if the lambda expression is under a function call expression. If so, we use the types of // the parameters. parentCall, hasParentCall := node.TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) if hasParentCall { return sb.getFunctionCallArgumentTypes(parentCall, context), true } // Check if the lambda expression is under a variable declaration. If so, we try to infer from // either its declared type or its use(s). parentVariable, hasParentVariable := node.TryGetIncomingNode(parser.NodeVariableStatementExpression) if !hasParentVariable { return make([]typegraph.TypeReference, 0), false } // Check if the parent variable has a declared type of function. If so, then we simply // use the declared parameter types. declaredType, hasDeclaredType := sb.getDeclaredVariableType(parentVariable) if hasDeclaredType && declaredType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { return declaredType.Parameters(), true } // Otherwise, we find all references of the variable under the parent scope that are, // themselves, under a function call, and intersect the types of arguments found. parentVariableName := parentVariable.Get(parser.NodeVariableStatementName) parentBlock, hasParentBlock := parentVariable.TryGetIncomingNode(parser.NodeStatementBlockStatement) if !hasParentBlock { return make([]typegraph.TypeReference, 0), false } var inferredTypes = make([]typegraph.TypeReference, 0) rit := sb.sg.srg.FindReferencesInScope(parentVariableName, parentBlock) for rit.Next() { funcCall, hasFuncCall := rit.Node().TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) if !hasFuncCall { continue } inferredTypes = sb.sg.tdg.IntersectTypes(inferredTypes, sb.getFunctionCallArgumentTypes(funcCall, context)) } return inferredTypes, true } // Resolve the inferred types and decorate the parameters with them (if any). inferredTypes, hasInferredTypes := getInferredTypes() if hasInferredTypes { for index, inferenceParameter := range inferenceParameters { var inferredType = sb.sg.tdg.AnyTypeReference() if index < len(inferredTypes) { if !inferredTypes[index].IsVoid() { inferredType = inferredTypes[index] } } sb.inferredParameterTypes.Set(string(inferenceParameter.NodeId), inferredType) sb.modifier.Modify(inferenceParameter).DecorateWithTagged(NodePredicateInferredType, inferredType) } } else { for _, inferenceParameter := range inferenceParameters { sb.inferredParameterTypes.Set(string(inferenceParameter.NodeId), sb.sg.tdg.AnyTypeReference()) sb.modifier.Modify(inferenceParameter).DecorateWithTagged(NodePredicateInferredType, sb.sg.tdg.AnyTypeReference()) } } }
// buildSmlExpression builds the CodeDOM for a SML expression. func (db *domBuilder) buildSmlExpression(node compilergraph.GraphNode) codedom.Expression { smlScope, _ := db.scopegraph.GetScope(node) funcOrTypeRefNode := node.GetNode(parser.NodeSmlExpressionTypeOrFunction) funcOrTypeRefScope, _ := db.scopegraph.GetScope(funcOrTypeRefNode) // Build the expression for the function or constructor to be called to construct the expression. var declarationFunction = db.buildExpression(funcOrTypeRefNode) var declarationFunctionType = db.scopegraph.TypeGraph().AnyTypeReference() if smlScope.HasLabel(proto.ScopeLabel_SML_CONSTRUCTOR) { constructor, _ := funcOrTypeRefScope.StaticTypeRef(db.scopegraph.TypeGraph()).ResolveMember("Declare", typegraph.MemberResolutionStatic) declarationFunctionType = constructor.MemberType() declarationFunction = codedom.MemberReference(declarationFunction, constructor, node) } else { declarationFunctionType = funcOrTypeRefScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) } var declarationArguments = make([]codedom.Expression, 0) declFunctionParams := declarationFunctionType.Parameters() // If the SML expression expects any attributes, then construct the props struct, class or mapping. if len(declFunctionParams) > 0 { attributeExpressions := map[string]codedom.Expression{} ait := node.StartQuery(). Out(parser.NodeSmlExpressionAttribute). BuildNodeIterator() for ait.Next() { attributeNode := ait.Node() attributeName := attributeNode.Get(parser.NodeSmlAttributeName) attributeExpressions[attributeName] = db.getExpressionOrDefault( attributeNode, parser.NodeSmlAttributeValue, codedom.NominalWrapping( codedom.LiteralValue("true", attributeNode), db.scopegraph.TypeGraph().BoolType(), attributeNode)) } // Construct the props object expression, either as a struct, class or as a mapping. propsType := declFunctionParams[0] if smlScope.HasLabel(proto.ScopeLabel_SML_PROPS_MAPPING) { declarationArguments = append(declarationArguments, db.buildMappingInitializerExpression(propsType, attributeExpressions, node)) } else { declarationArguments = append(declarationArguments, db.buildStructInitializerExpression(propsType, attributeExpressions, node)) } } // If the SML expression expects any children, then construct the children stream or value (if any). if len(declFunctionParams) > 1 { cit := node.StartQuery(). Out(parser.NodeSmlExpressionChild). BuildNodeIterator() children := db.buildExpressions(cit, buildExprNormally) switch { case smlScope.HasLabel(proto.ScopeLabel_SML_SINGLE_CHILD): // If there is a child, it is a value. If missing, the child is nullable, so we don't put // anything there. if len(children) > 0 { declarationArguments = append(declarationArguments, children[0]) } case smlScope.HasLabel(proto.ScopeLabel_SML_STREAM_CHILD): // Single child which is a stream. Passed directly to the declaring function as an argument. declarationArguments = append(declarationArguments, children[0]) case smlScope.HasLabel(proto.ScopeLabel_SML_CHILDREN): // The argument is a stream of the child expressions. Build it as a generator. if len(children) == 0 { // Add an empty generator. declarationArguments = append(declarationArguments, codedom.RuntimeFunctionCall(codedom.EmptyGeneratorDirect, []codedom.Expression{}, node)) } else { yielders := make([]codedom.Statement, len(children)) for index, child := range children { yielders[index] = codedom.YieldValue(child, child.BasisNode()) if index > 0 { yielders[index-1].(codedom.HasNextStatement).SetNext(yielders[index]) } } generatorFunc := codedom.FunctionDefinition([]string{}, []string{}, yielders[0], false, codedom.GeneratorFunction, node) generatorExpr := codedom.FunctionCall(generatorFunc, []codedom.Expression{}, node) declarationArguments = append(declarationArguments, codedom.AwaitPromise(generatorExpr, node)) } default: panic("Unknown SML children scope label") } } // The value of declaration function invoked with the arguments is the initial definition. var definition = codedom.AwaitPromise( codedom.FunctionCall(declarationFunction, declarationArguments, node), node) // Add any decorators as wrapping around the definition call. dit := node.StartQuery(). Out(parser.NodeSmlExpressionDecorator). BuildNodeIterator() for dit.Next() { decoratorNode := dit.Node() decoratorFunc := db.getExpression(decoratorNode, parser.NodeSmlDecoratorPath) // The value for the decorator is either the value expression given or the literal // value "true" if none specified. decoratorValue := db.getExpressionOrDefault( decoratorNode, parser.NodeSmlDecoratorValue, codedom.NominalWrapping( codedom.LiteralValue("true", decoratorNode), db.scopegraph.TypeGraph().BoolType(), decoratorNode)) // The decorator is invoked over the definition. decoratorArgs := []codedom.Expression{definition, decoratorValue} definition = codedom.AwaitPromise( codedom.FunctionCall(decoratorFunc, decoratorArgs, decoratorNode), decoratorNode) } return definition }
// scopeGenericSpecifierExpression scopes a generic specifier in the SRG. func (sb *scopeBuilder) scopeGenericSpecifierExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the child expression. childScope := sb.getScope(node.GetNode(parser.NodeGenericSpecifierChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } if childScope.GetKind() != proto.ScopeKind_GENERIC { sb.decorateWithError(node, "Cannot apply generics to non-generic scope") return newScope().Invalid().GetScope() } // Retrieve the underlying named scope. namedScope, isNamedScope := sb.getNamedScopeForScope(childScope) if !isNamedScope { panic("Generic non-named scope") } var genericType = sb.sg.tdg.VoidTypeReference() if namedScope.IsStatic() { genericType = namedScope.StaticType(context) } else { genericType = childScope.GenericTypeRef(sb.sg.tdg) } genericsToReplace := namedScope.Generics() if len(genericsToReplace) == 0 { sb.decorateWithError(node, "Cannot apply generics to non-generic type %v", genericType) return newScope().Invalid().GetScope() } git := node.StartQuery(). Out(parser.NodeGenericSpecifierType). BuildNodeIterator() var genericIndex = 0 for git.Next() { // Ensure we haven't gone outside the required number of generics. if genericIndex >= len(genericsToReplace) { genericIndex++ continue } // Build the type to use in place of the generic. replacementType, gerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(git.Node())) if gerr != nil { sb.decorateWithError(node, "Error on type #%v in generic specifier: %v", gerr, genericIndex+1) return newScope().Invalid().GetScope() } // Ensure that the type meets the generic constraint. toReplace := genericsToReplace[genericIndex] if serr := replacementType.CheckSubTypeOf(toReplace.Constraint()); serr != nil { sb.decorateWithError(node, "Cannot use type %v as generic %v (#%v) over %v %v: %v", replacementType, toReplace.Name(), genericIndex+1, namedScope.Title(), namedScope.Name(), serr) return newScope().Invalid().GetScope() } // If the parent type is structural, ensure the constraint is structural. if genericType.IsStructurual() { if serr := replacementType.EnsureStructural(); serr != nil { sb.decorateWithError(node, "Cannot use type %v as generic %v (#%v) over %v %v: %v", replacementType, toReplace.Name(), genericIndex+1, namedScope.Title(), namedScope.Name(), serr) return newScope().Invalid().GetScope() } } // Replace the generic with the associated type. genericType = genericType.ReplaceType(toReplace.AsType(), replacementType) genericIndex = genericIndex + 1 } if genericIndex != len(genericsToReplace) { sb.decorateWithError(node, "Generic count must match. Found: %v, expected: %v on %v %v", genericIndex, len(genericsToReplace), namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() } // Save the updated type. if namedScope.IsStatic() { return newScope(). Valid(). ForNamedScope(namedScope, context). WithStaticType(genericType). WithKind(proto.ScopeKind_STATIC). GetScope() } else { return newScope(). Valid(). ForNamedScope(namedScope, context). Resolving(genericType). WithKind(proto.ScopeKind_VALUE). GetScope() } }
// scopeSmlExpression scopes an SML expression in the SRG. func (sb *scopeBuilder) scopeSmlExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the type or function and ensure it refers to a "declarable" type or function. // A type is "declarable" if it has a constructor named Declare with a "declarable" function type signature. // // A function is "declarable" if it has the following properties: // - Must return a non-void type // - Parameter #1 represents the properties (attributes) and must be: // 1) A struct // 2) A class with at least one field // 3) A mapping // // - Parameter #2 (optional) represents the children (contents). typeOrFuncScope := sb.getScope(node.GetNode(parser.NodeSmlExpressionTypeOrFunction), context) if !typeOrFuncScope.GetIsValid() { return newScope().Invalid().GetScope() } var functionType = sb.sg.tdg.AnyTypeReference() var declarationLabel = proto.ScopeLabel_SML_FUNCTION var childrenLabel = proto.ScopeLabel_SML_NO_CHILDREN var propsLabel = proto.ScopeLabel_SML_PROPS_MAPPING switch typeOrFuncScope.GetKind() { case proto.ScopeKind_VALUE: // Function. functionType = typeOrFuncScope.ResolvedTypeRef(sb.sg.tdg) declarationLabel = proto.ScopeLabel_SML_FUNCTION calledFunctionScope, _ := sb.getNamedScopeForScope(typeOrFuncScope) context.staticDependencyCollector.registerNamedDependency(calledFunctionScope) case proto.ScopeKind_STATIC: // Type. Ensure it has a Declare constructor. module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) staticType := typeOrFuncScope.StaticTypeRef(sb.sg.tdg) declareConstructor, rerr := staticType.ResolveAccessibleMember("Declare", module, typegraph.MemberResolutionStatic) if rerr != nil { sb.decorateWithError(node, "A type used in a SML declaration tag must have a 'Declare' constructor: %v", rerr) return newScope().Invalid().GetScope() } context.staticDependencyCollector.registerDependency(declareConstructor) functionType = declareConstructor.MemberType() declarationLabel = proto.ScopeLabel_SML_CONSTRUCTOR case proto.ScopeKind_GENERIC: sb.decorateWithError(node, "A generic type cannot be used in a SML declaration tag") return newScope().Invalid().GetScope() default: panic("Unknown scope kind") } // Ensure the function type is a function. if !functionType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { sb.decorateWithError(node, "Declared reference in an SML declaration tag must be a function. Found: %v", functionType) return newScope().Invalid().GetScope() } // Ensure the function doesn't return void. declaredType := functionType.Generics()[0] if declaredType.IsVoid() { sb.decorateWithError(node, "Declarable function used in an SML declaration tag cannot return void") return newScope().Invalid().GetScope() } parameters := functionType.Parameters() // Check for attributes. var isValid = true var resolvedType = declaredType if _, ok := node.TryGetNode(parser.NodeSmlExpressionAttribute); ok || len(parameters) >= 1 { if len(parameters) < 1 { sb.decorateWithError(node, "Declarable function or constructor used in an SML declaration tag with attributes must have a 'props' parameter as parameter #1. Found: %v", functionType) return newScope().Invalid().Resolving(declaredType).GetScope() } propsType := parameters[0] // Ensure that the first parameter is either structural, a class with ForProps or a Mapping. if propsType.NullValueAllowed() { sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag cannot allow null values. Found: %v", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } switch { case propsType.IsDirectReferenceTo(sb.sg.tdg.MappingType()): // Mappings are always allowed. propsLabel = proto.ScopeLabel_SML_PROPS_MAPPING case propsType.IsRefToStruct(): // Structs are always allowed. propsLabel = proto.ScopeLabel_SML_PROPS_STRUCT case propsType.IsRefToClass(): // Classes are allowed if they have at least one field. if len(propsType.ReferredType().Fields()) == 0 { sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag has type %v, which does not have any settable fields; use an empty `struct` instead if this is the intended behavior", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } propsLabel = proto.ScopeLabel_SML_PROPS_CLASS default: // Otherwise, the type is not valid. sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag must be a struct, a class with a ForProps constructor or a Mapping. Found: %v", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } // At this point we know we have a declarable function used in the tag. // Scope the attributes to match the props type. ait := node.StartQuery(). Out(parser.NodeSmlExpressionAttribute). BuildNodeIterator() attributesEncountered := map[string]bool{} for ait.Next() { attributeName, ok := sb.scopeSmlAttribute(ait.Node(), propsType, context) attributesEncountered[attributeName] = true isValid = isValid && ok } // If the props type is not a mapping, ensure that all required fields were set. if !propsType.IsDirectReferenceTo(sb.sg.tdg.MappingType()) { for _, requiredField := range propsType.ReferredType().RequiredFields() { if _, ok := attributesEncountered[requiredField.Name()]; !ok { sb.decorateWithError(node, "Required attribute '%v' is missing for SML declaration props type %v", requiredField.Name(), propsType) isValid = false } } } } // Scope decorators. dit := node.StartQuery(). Out(parser.NodeSmlExpressionDecorator). BuildNodeIterator() for dit.Next() { decoratorReturnType, ok := sb.scopeSmlDecorator(dit.Node(), resolvedType, context) resolvedType = decoratorReturnType isValid = isValid && ok } // Scope the children to match the childs type. if _, ok := node.TryGetNode(parser.NodeSmlExpressionChild); ok || len(parameters) >= 2 { if len(parameters) < 2 { sb.decorateWithError(node, "Declarable function or constructor used in an SML declaration tag with children must have a 'children' parameter. Found: %v", functionType) return newScope().Invalid().Resolving(resolvedType).GetScope() } childsType := parameters[1] // Scope and collect the types of all the children. var childrenTypes = make([]typegraph.TypeReference, 0) cit := node.StartQuery(). Out(parser.NodeSmlExpressionChild). BuildNodeIterator() for cit.Next() { childNode := cit.Node() childScope := sb.getScope(childNode, context) if !childScope.GetIsValid() { isValid = false } childrenTypes = append(childrenTypes, childScope.ResolvedTypeRef(sb.sg.tdg)) } // Check if the children parameter is a stream. If so, we match the stream type. Otherwise, // we check if the child matches the value expected. if childsType.IsDirectReferenceTo(sb.sg.tdg.StreamType()) { // If there is a single child that is also a matching stream, then we allow it. if len(childrenTypes) == 1 && childrenTypes[0] == childsType { childrenLabel = proto.ScopeLabel_SML_STREAM_CHILD } else { childrenLabel = proto.ScopeLabel_SML_CHILDREN streamValueType := childsType.Generics()[0] // Ensure the child types match. for index, childType := range childrenTypes { if serr := childType.CheckSubTypeOf(streamValueType); serr != nil { sb.decorateWithError(node, "Child #%v under SML declaration must be subtype of %v: %v", index+1, streamValueType, serr) isValid = false } } } } else if len(childrenTypes) > 1 { // Since not a slice, it can only be one or zero children. sb.decorateWithError(node, "SML declaration tag allows at most a single child. Found: %v", len(childrenTypes)) return newScope().Invalid().Resolving(resolvedType).GetScope() } else { childrenLabel = proto.ScopeLabel_SML_SINGLE_CHILD // If the child type is not nullable, then we need to make sure a value was specified. if !childsType.NullValueAllowed() && len(childrenTypes) < 1 { sb.decorateWithError(node, "SML declaration tag requires a single child. Found: %v", len(childrenTypes)) return newScope().Invalid().Resolving(resolvedType).GetScope() } // Ensure the child type matches. if len(childrenTypes) == 1 { if serr := childrenTypes[0].CheckSubTypeOf(childsType); serr != nil { sb.decorateWithError(node, "SML declaration tag requires a child of type %v: %v", childsType, serr) return newScope().Invalid().Resolving(resolvedType).GetScope() } } } } return newScope(). IsValid(isValid). Resolving(resolvedType). WithLabel(declarationLabel). WithLabel(propsLabel). WithLabel(childrenLabel). GetScope() }