// scopeInCollectionExpression scopes an 'in' collection expression in the SRG. func (sb *scopeBuilder) scopeInCollectionExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the left and right expressions. leftScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionLeftExpr), context) rightScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionRightExpr), context) // Ensure that both scopes are valid. if !leftScope.GetIsValid() || !rightScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure that the right side has a 'contains' operator defined. rightType := rightScope.ResolvedTypeRef(sb.sg.tdg) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := rightType.ResolveAccessibleMember("contains", module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator 'contains' is not defined on type '%v'", rightType) return newScope().Invalid().GetScope() } // Ensure the right side is not nullable. if rightType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator 'in' on nullable value of type '%v'", rightType) return newScope().Invalid().GetScope() } // Ensure that the left side can be used as the operator's parameter. parameterType := operator.ParameterTypes()[0].TransformUnder(rightType) leftType := leftScope.ResolvedTypeRef(sb.sg.tdg) if serr := leftType.CheckSubTypeOf(parameterType); serr != nil { sb.decorateWithError(node, "Cannot invoke operator 'in' with value of type '%v': %v", leftType, serr) return newScope().Invalid().GetScope() } return newScope().Valid().CallsOperator(operator).Resolving(sb.sg.tdg.BoolTypeReference()).GetScope() }
// scopeAssignedValue scopes a named assigned value exported into the context. func (sb *scopeBuilder) scopeAssignedValue(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // If the assigned value's name is _, then it is anonymous scope. if node.Get(parser.NodeNamedValueName) == ANONYMOUS_REFERENCE { return newScope().ForAnonymousScope(sb.sg.tdg).GetScope() } // If the assigned value is under a rejection, then it is always an error (but nullable, as it // may not be present always). if _, ok := node.TryGetIncomingNode(parser.NodeAssignedRejection); ok { return newScope().Valid().Assignable(sb.sg.tdg.ErrorTypeReference().AsNullable()).GetScope() } // Otherwise, the value is the assignment of the parent statement's expression. parentNode := node.GetIncomingNode(parser.NodeAssignedDestination) switch parentNode.Kind() { case parser.NodeTypeResolveStatement: // The assigned value exported by a resolve statement has the type of its expression. exprScope := sb.getScope(parentNode.GetNode(parser.NodeResolveStatementSource), context) if !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } // If the parent node has a rejection, then the expression may be null. exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) if _, ok := parentNode.TryGetNode(parser.NodeAssignedRejection); ok { exprType = exprType.AsNullable() } return newScope().Valid().Assignable(exprType).GetScope() default: panic(fmt.Sprintf("Unknown node exporting an assigned value: %v", parentNode.Kind())) return newScope().Invalid().GetScope() } }
// scopeUnaryExpression scopes a unary expression in the SRG. func (sb *scopeBuilder) scopeUnaryExpression(node compilergraph.GraphNode, opName string, predicate compilergraph.Predicate, context scopeContext) *scopeInfoBuilder { // Get the scope of the sub expression. childScope := sb.getScope(node.GetNode(predicate), context) // Ensure that the child scope is valid. if !childScope.GetIsValid() { return newScope().Invalid() } // Ensure that the operator exists under the resolved type. childType := childScope.ResolvedTypeRef(sb.sg.tdg) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := childType.ResolveAccessibleMember(opName, module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator '%v' is not defined on type '%v'", opName, childType) return newScope().Invalid() } returnType, _ := operator.ReturnType() // Check for nullable values. if childType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator '%v' on nullable type '%v'", opName, childType) return newScope().Invalid().CallsOperator(operator).Resolving(returnType.TransformUnder(childType)) } return newScope().Valid().CallsOperator(operator).Resolving(returnType.TransformUnder(childType)) }
// buildStringLiteral builds the CodeDOM for a string literal. func (db *domBuilder) buildStringLiteral(node compilergraph.GraphNode) codedom.Expression { stringValueStr := node.Get(parser.NodeStringLiteralExpressionValue) if stringValueStr[0] == '`' { unquoted := stringValueStr[1 : len(stringValueStr)-1] stringValueStr = strconv.Quote(unquoted) } return codedom.NominalWrapping(codedom.LiteralValue(stringValueStr, node), db.scopegraph.TypeGraph().StringType(), node) }
// buildStreamMemberAccessExpression builds the CodeDOM for a stream member access expression (*.) func (db *domBuilder) buildStreamMemberAccessExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeMemberAccessChildExpr) memberName := codedom.LiteralValue("'"+node.Get(parser.NodeMemberAccessIdentifier)+"'", node) return codedom.RuntimeFunctionCall(codedom.StreamMemberAccessFunction, []codedom.Expression{childExpr, memberName}, node, ) }
// buildVarStatement builds the CodeDOM for a variable statement. func (db *domBuilder) buildVarStatement(node compilergraph.GraphNode) codedom.Statement { name := node.Get(parser.NodeVariableStatementName) initExpr, _ := db.tryGetExpression(node, parser.NodeVariableStatementExpression) if initExpr == nil { // If no init expression was specified, then the variable is initialized to null. initExpr = codedom.LiteralValue("null", node) } return codedom.VarDefinitionWithInit(name, initExpr, node) }
// scopeMemberAccessExpression scopes a member access expression in the SRG. func (sb *scopeBuilder) scopeMemberAccessExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeMemberAccessChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } memberName := node.Get(parser.NodeMemberAccessIdentifier) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) switch childScope.GetKind() { case proto.ScopeKind_VALUE: childType := childScope.ResolvedTypeRef(sb.sg.tdg) if childType.IsNullable() { sb.decorateWithError(node, "Cannot access name '%v' under nullable type '%v'. Please use the ?. operator to ensure type safety.", memberName, childType) return newScope().Invalid().GetScope() } typeMember, rerr := childType.ResolveAccessibleMember(memberName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } memberScope := sb.getNamedScopeForMember(typeMember) context.staticDependencyCollector.checkNamedScopeForDependency(memberScope) return newScope().ForNamedScopeUnderType(memberScope, childType, context).GetScope() case proto.ScopeKind_GENERIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt member access of '%v' under %v %v, as it is generic without specification", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() case proto.ScopeKind_STATIC: staticType := childScope.StaticTypeRef(sb.sg.tdg) namedScope, _ := sb.getNamedScopeForScope(childScope) memberScope, rerr := namedScope.ResolveStaticMember(memberName, module, staticType) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } return newScope().ForNamedScopeUnderType(memberScope, staticType, context).GetScope() default: panic("Unknown scope kind") } return newScope().Invalid().GetScope() }
// scopeStreamMemberAccessExpression scopes a stream member access expression in the SRG. func (sb *scopeBuilder) scopeStreamMemberAccessExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeMemberAccessChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } memberName := node.Get(parser.NodeMemberAccessIdentifier) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) switch childScope.GetKind() { case proto.ScopeKind_VALUE: childType := childScope.ResolvedTypeRef(sb.sg.tdg) // Ensure the child type is a stream. generics, serr := childType.CheckConcreteSubtypeOf(sb.sg.tdg.StreamType()) if serr != nil { sb.decorateWithError(node, "Cannot attempt stream access of name '%v' under non-stream type '%v': %v", memberName, childType, serr) return newScope().Invalid().GetScope() } valueType := generics[0] typeMember, rerr := valueType.ResolveAccessibleMember(memberName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } memberScope := sb.getNamedScopeForMember(typeMember) context.staticDependencyCollector.checkNamedScopeForDependency(memberScope) return newScope().ForNamedScopeUnderModifiedType(memberScope, valueType, makeStream, context).GetScope() case proto.ScopeKind_GENERIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt stream member access of '%v' under %v %v, as it is generic without specification", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() case proto.ScopeKind_STATIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt stream member access of '%v' under %v %v, as it is a static type", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() default: panic("Unknown scope kind") } return newScope().Invalid().GetScope() }
// scopeNumericLiteralExpression scopes a numeric literal expression in the SRG. func (sb *scopeBuilder) scopeNumericLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { numericValueStr := node.Get(parser.NodeNumericLiteralExpressionValue) _, isNotInt := strconv.ParseInt(numericValueStr, 0, 64) if isNotInt == nil { return newScope(). Valid(). Resolving(sb.sg.tdg.NewTypeReference(sb.sg.tdg.IntType())). GetScope() } else { return newScope(). Valid(). Resolving(sb.sg.tdg.NewTypeReference(sb.sg.tdg.FloatType())). GetScope() } }
// FindNameInScope finds the given name accessible from the scope under which the given node exists, if any. func (g *SRG) FindNameInScope(name string, node compilergraph.GraphNode) (SRGScopeOrImport, bool) { // Attempt to resolve the name as pointing to a parameter, var statement, loop var or with var. srgNode, srgNodeFound := g.findAddedNameInScope(name, node) if srgNodeFound { return SRGNamedScope{srgNode, g}, true } // If still not found, try to resolve as a type or import. nodeSource := node.Get(parser.NodePredicateSource) parentModule, parentModuleFound := g.FindModuleBySource(compilercommon.InputSource(nodeSource)) if !parentModuleFound { panic(fmt.Sprintf("Missing module for source %v", nodeSource)) } // Try to resolve as a local member. srgTypeOrMember, typeOrMemberFound := parentModule.FindTypeOrMemberByName(name, ModuleResolveAll) if typeOrMemberFound { return SRGNamedScope{srgTypeOrMember.GraphNode, g}, true } // Try to resolve as an imported member. localImportNode, localImportFound := parentModule.findImportWithLocalName(name) if localImportFound { // Retrieve the package for the imported member. packageInfo := g.getPackageForImport(localImportNode) resolutionName := localImportNode.Get(parser.NodeImportPredicateSubsource) // If an SRG package, then continue with the resolution. Otherwise, // we return a named scope that says that the name needs to be furthered // resolved in the package by the type graph. if packageInfo.IsSRGPackage() { packageTypeOrMember, packagetypeOrMemberFound := packageInfo.FindTypeOrMemberByName(resolutionName) if packagetypeOrMemberFound { return SRGNamedScope{packageTypeOrMember.GraphNode, g}, true } } return SRGExternalPackageImport{packageInfo.packageInfo, resolutionName, g}, true } // Try to resolve as an imported package. importNode, importFound := parentModule.findImportByPackageName(name) if importFound { return SRGNamedScope{importNode, g}, true } return SRGNamedScope{}, false }
// scopeIdentifierExpression scopes an identifier expression in the SRG. func (sb *scopeBuilder) scopeIdentifierExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { name := node.Get(parser.NodeIdentifierExpressionName) if name == ANONYMOUS_REFERENCE { // Make sure this node is under an assignment of some kind. var found = false for _, predicate := range ALLOWED_ANONYMOUS { if _, ok := node.TryGetIncomingNode(predicate); ok { found = true break } } if !found { sb.decorateWithError(node, "Anonymous identifier '_' cannot be used as a value") return newScope().Invalid().GetScope() } return newScope().ForAnonymousScope(sb.sg.tdg).GetScope() } // Lookup the name given, starting at the location of the current node. namedScope, rerr := sb.lookupNamedScope(name, node) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } // Ensure that the named scope has a valid type. if !namedScope.IsValid(context) { return newScope().Invalid().GetScope() } // Warn if we are accessing an assignable value under an async function, as it will be executing // in a different context. if namedScope.IsAssignable() && namedScope.UnderModule() { srgImpl, found := context.getParentContainer(sb.sg.srg) if found && srgImpl.ContainingMember().IsAsyncFunction() { sb.decorateWithWarning(node, "%v '%v' is defined outside the async function and will therefore be unique for each call to this function", namedScope.Title(), name) } } context.staticDependencyCollector.checkNamedScopeForDependency(namedScope) return newScope().ForNamedScope(namedScope, context).GetScope() }
// FindReferencesInScope finds all identifier expressions that refer to the given name, under the given // scope. func (g *SRG) FindReferencesInScope(name string, node compilergraph.GraphNode) compilergraph.NodeIterator { // Note: This filter ensures that the name is accessible in the scope of the given node by checking that // the node referencing the name is contained by the given node. containingFilter := func(q compilergraph.GraphQuery) compilergraph.Query { startRune := node.GetValue(parser.NodePredicateStartRune).Int() endRune := node.GetValue(parser.NodePredicateEndRune).Int() return q. HasWhere(parser.NodePredicateStartRune, compilergraph.WhereGT, startRune). HasWhere(parser.NodePredicateEndRune, compilergraph.WhereLT, endRune) } return g.layer.StartQuery(name). In(parser.NodeIdentifierExpressionName). IsKind(parser.NodeTypeIdentifierExpression). Has(parser.NodePredicateSource, node.Get(parser.NodePredicateSource)). FilterBy(containingFilter). BuildNodeIterator() }
// scopeStructuralNewExpressionEntry scopes a single entry in a structural new expression. func (sb *scopeBuilder) scopeStructuralNewExpressionEntry(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { parentNode := node.GetIncomingNode(parser.NodeStructuralNewExpressionChildEntry) parentExprScope := sb.getScope(parentNode.GetNode(parser.NodeStructuralNewTypeExpression), context) parentType := parentExprScope.StaticTypeRef(sb.sg.tdg) if parentExprScope.GetKind() == proto.ScopeKind_VALUE { parentType = parentExprScope.ResolvedTypeRef(sb.sg.tdg) } entryName := node.Get(parser.NodeStructuralNewEntryKey) // Get the scope for the value. valueScope := sb.getScope(node.GetNode(parser.NodeStructuralNewEntryValue), context) if !valueScope.GetIsValid() { return newScope().Invalid().GetScope() } // Lookup the member associated with the entry name. module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) member, rerr := parentType.ResolveAccessibleMember(entryName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } // Ensure the member is assignable. if member.IsReadOnly() { sb.decorateWithError(node, "%v %v under type %v is read-only", member.Title(), member.Name(), parentType) return newScope().Invalid().GetScope() } // Get the member's assignable type, transformed under the parent type, and ensure it is assignable // from the type of the value. assignableType := member.AssignableType().TransformUnder(parentType) valueType := valueScope.ResolvedTypeRef(sb.sg.tdg) if aerr := valueType.CheckSubTypeOf(assignableType); aerr != nil { sb.decorateWithError(node, "Cannot assign value of type %v to %v %v: %v", valueType, member.Title(), member.Name(), aerr) return newScope().Invalid().GetScope() } return newScope().ForNamedScope(sb.getNamedScopeForMember(member), context).Valid().GetScope() }
// scopeBinaryExpression scopes a binary expression in the SRG. func (sb *scopeBuilder) scopeBinaryExpression(node compilergraph.GraphNode, opName string, context scopeContext) *scopeInfoBuilder { // Get the scope of the left and right expressions. leftScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionLeftExpr), context) rightScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionRightExpr), context) // Ensure that both scopes are valid. if !leftScope.GetIsValid() || !rightScope.GetIsValid() { return newScope().Invalid() } // Ensure that both scopes have the same type. leftType := leftScope.ResolvedTypeRef(sb.sg.tdg) rightType := rightScope.ResolvedTypeRef(sb.sg.tdg) if leftType != rightType { sb.decorateWithError(node, "Operator '%v' requires operands of the same type. Found: '%v' and '%v'", opName, leftType, rightType) return newScope().Invalid() } // Ensure that the operator exists under the resolved type. module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := leftType.ResolveAccessibleMember(opName, module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator '%v' is not defined on type '%v'", opName, leftType) return newScope().Invalid() } returnType, _ := operator.ReturnType() // Check for nullable values. if leftType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator '%v' on nullable type '%v'", opName, leftType) return newScope().Invalid().CallsOperator(operator).Resolving(returnType.TransformUnder(leftType)) } if rightType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator '%v' on nullable type '%v'", opName, rightType) return newScope().Invalid().CallsOperator(operator).Resolving(returnType.TransformUnder(leftType)) } return newScope().Valid().CallsOperator(operator).Resolving(returnType.TransformUnder(leftType)) }
// scopeStructuralNewTypeExpression scopes a structural new expression for constructing a new instance // of a structural or class type. func (sb *scopeBuilder) scopeStructuralNewTypeExpression(node compilergraph.GraphNode, childScope *proto.ScopeInfo, context scopeContext) proto.ScopeInfo { // Retrieve the static type. staticTypeRef := childScope.StaticTypeRef(sb.sg.tdg) // Ensure that the static type is a struct OR it is a class with an accessible 'new'. staticType := staticTypeRef.ReferredType() switch staticType.TypeKind() { case typegraph.ClassType: // Classes can only be constructed structurally if they are in the same module as this call. // Otherwise, an exported constructor must be used. module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) _, rerr := staticTypeRef.ResolveAccessibleMember("new", module, typegraph.MemberResolutionStatic) if rerr != nil { sb.decorateWithError(node, "Cannot structurally construct type %v, as it is imported from another module", staticTypeRef) return newScope().Invalid().Resolving(staticTypeRef).GetScope() } case typegraph.StructType: // Structs can be constructed by anyone, assuming that their members are all exported. // That check occurs below. break default: sb.decorateWithError(node, "Cannot structurally construct type %v", staticTypeRef) return newScope().Invalid().Resolving(staticTypeRef).GetScope() } encountered, isValid := sb.scopeStructuralNewEntries(node, context) if !isValid { return newScope().Invalid().Resolving(staticTypeRef).GetScope() } // Ensure that all required entries are present. for _, field := range staticType.RequiredFields() { if _, ok := encountered[field.Name()]; !ok { isValid = false sb.decorateWithError(node, "Non-nullable %v '%v' is required to construct type %v", field.Title(), field.Name(), staticTypeRef) } } return newScope().IsValid(isValid).Resolving(staticTypeRef).GetScope() }
// scopeSmlNormalAttribute scopes an SML expression attribute under a declaration. func (sb *scopeBuilder) scopeSmlAttribute(node compilergraph.GraphNode, propsType typegraph.TypeReference, context scopeContext) (string, bool) { attributeName := node.Get(parser.NodeSmlAttributeName) // If the props type is a struct or class, ensure that the attribute name exists. var allowedValueType = sb.sg.tdg.AnyTypeReference() if propsType.IsRefToStruct() || propsType.IsRefToClass() { module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) resolvedMember, rerr := propsType.ResolveAccessibleMember(attributeName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return attributeName, false } allowedValueType = resolvedMember.AssignableType() } else { // The props type must be a mapping, so the value must match it value type. allowedValueType = propsType.Generics()[0] } // Scope the attribute value (if any). If none, then we default to a boolean value. var attributeValueType = sb.sg.tdg.BoolTypeReference() valueNode, hasValueNode := node.TryGetNode(parser.NodeSmlAttributeValue) if hasValueNode { attributeValueScope := sb.getScope(valueNode, context) if !attributeValueScope.GetIsValid() { return attributeName, false } attributeValueType = attributeValueScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure it matches the assignable value type. if serr := attributeValueType.CheckSubTypeOf(allowedValueType); serr != nil { sb.decorateWithError(node, "Cannot assign value of type %v for attribute %v: %v", attributeValueType, attributeName, serr) return attributeName, false } return attributeName, true }
// getPackageForImport returns the package information for the package imported by the given import // package node. func (g *SRG) getPackageForImport(importPackageNode compilergraph.GraphNode) importedPackage { importNode := importPackageNode.GetIncomingNode(parser.NodeImportPredicatePackageRef) // Note: There may not be a kind, in which case this will return empty string, which is the // default kind. packageKind, _ := importNode.TryGet(parser.NodeImportPredicateKind) packageLocation := importNode.Get(parser.NodeImportPredicateLocation) packageInfo, ok := g.packageMap.Get(packageKind, packageLocation) if !ok { source := importNode.Get(parser.NodeImportPredicateSource) subsource, _ := importPackageNode.TryGet(parser.NodeImportPredicateSubsource) panic(fmt.Sprintf("Missing package info for import %s %s (reference %v) (node %v)\nPackage Map: %v", source, subsource, packageLocation, importNode, g.packageMap)) } return importedPackage{ srg: g, packageInfo: packageInfo, importSource: compilercommon.InputSource(importPackageNode.Get(parser.NodePredicateSource)), } }
// scopeDeclaredValue scopes a declared value (variable statement, variable member, type field). func (sb *scopeBuilder) scopeDeclaredValue(node compilergraph.GraphNode, title string, option requiresInitializerOption, exprPredicate compilergraph.Predicate, context scopeContext) proto.ScopeInfo { var exprScope *proto.ScopeInfo = nil exprNode, hasExpression := node.TryGetNode(exprPredicate) if hasExpression { // Scope the expression. exprScope = sb.getScope(exprNode, context) if !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } } // Load the declared type, if any. declaredType, hasDeclaredType := sb.getDeclaredVariableType(node) if !hasDeclaredType { if exprScope == nil { panic("Somehow ended up with no declared type and no expr scope") } return newScope().Valid().AssignableResolvedTypeOf(exprScope).GetScope() } // Compare against the type of the expression. if hasExpression { exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) if serr := exprType.CheckSubTypeOf(declaredType); serr != nil { sb.decorateWithError(node, "%s '%s' has declared type '%v': %v", title, node.Get(parser.NodeVariableStatementName), declaredType, serr) return newScope().Invalid().GetScope() } } else if option == requiresInitializer { // Make sure if the type is non-nullable that there is an expression. if !declaredType.IsNullable() { sb.decorateWithError(node, "%s '%s' must have explicit initializer as its type '%v' is non-nullable", title, node.Get(parser.NodeVariableStatementName), declaredType) return newScope().Invalid().Assignable(declaredType).GetScope() } } return newScope().Valid().Assignable(declaredType).GetScope() }
// buildNumericLiteral builds the CodeDOM for a numeric literal. func (db *domBuilder) buildNumericLiteral(node compilergraph.GraphNode) codedom.Expression { numericValueStr := node.Get(parser.NodeNumericLiteralExpressionValue) if strings.HasSuffix(numericValueStr, "f") { numericValueStr = numericValueStr[0 : len(numericValueStr)-1] } // Handle binary. if strings.HasPrefix(numericValueStr, "0b") || strings.HasPrefix(numericValueStr, "0B") { parsed, _ := strconv.ParseInt(numericValueStr[2:], 2, 64) numericValueStr = strconv.Itoa(int(parsed)) } // Note: Handles Hex. intValue, isNotInt := strconv.ParseInt(numericValueStr, 0, 64) if isNotInt == nil { numericValueStr = strconv.Itoa(int(intValue)) } exprScope, _ := db.scopegraph.GetScope(node) numericType := exprScope.ResolvedTypeRef(db.scopegraph.TypeGraph()).ReferredType() return codedom.NominalWrapping(codedom.LiteralValue(numericValueStr, node), numericType, node) }
// scopeSliceChildExpression scopes the child expression of a slice expression, returning whether it // is valid and the associated operator found, if any. func (sb *scopeBuilder) scopeSliceChildExpression(node compilergraph.GraphNode, opName string, context scopeContext) (typegraph.TGMember, typegraph.TypeReference, bool) { // Scope the child expression of the slice. childScope := sb.getScope(node.GetNode(parser.NodeSliceExpressionChildExpr), context) if !childScope.GetIsValid() { return typegraph.TGMember{}, sb.sg.tdg.AnyTypeReference(), false } childType := childScope.ResolvedTypeRef(sb.sg.tdg) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := childType.ResolveAccessibleMember(opName, module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator '%v' is not defined on type '%v'", opName, childType) return typegraph.TGMember{}, childType, false } // Ensure that the child expression is not nullable. if childType.NullValueAllowed() { sb.decorateWithError(node, "Operator '%v' cannot be called on nullable type '%v'", opName, childType) return typegraph.TGMember{}, childType, false } return operator, childType, true }
// buildLocalizedRefValue returns a string value for representing a localized type reference. func buildLocalizedRefValue(genericNode compilergraph.GraphNode) string { var buffer bytes.Buffer genericKind := genericNode.Get(NodePredicateGenericKind) genericIndex := genericNode.Get(NodePredicateGenericIndex) localId := "local:" + genericKind + ":" + genericIndex // Referenced type ID. For localized types, this is the generic index and kind. buffer.WriteByte('[') buffer.WriteString(localId) buffer.WriteString(strings.Repeat(" ", trhSlotTypeId.length-len(localId))) buffer.WriteByte(']') // Flags: special and nullable. buffer.WriteByte(specialFlagLocal) buffer.WriteByte(nullableFlagFalse) // Generic count and parameter count. buffer.WriteString(padNumberToString(0, trhSlotGenericCount.length)) buffer.WriteString(padNumberToString(0, trhSlotParameterCount.length)) return buffer.String() }
// ResolutionPath returns the full resolution path for this type reference. // Panics if this is not a RefKind of TypeRefPath. func (t SRGTypeRef) ResolutionPath() string { compilerutil.DCHECK(func() bool { return t.RefKind() == TypeRefPath }, "Expected type ref path") var resolvePathPieces = make([]string, 0) var currentPath compilergraph.GraphNode = t.GraphNode. GetNode(parser.NodeTypeReferencePath). GetNode(parser.NodeIdentifierPathRoot) for { // Add the path piece to the array. name := currentPath.Get(parser.NodeIdentifierAccessName) resolvePathPieces = append([]string{name}, resolvePathPieces...) // If there is a source, continue searching. source, found := currentPath.TryGetNode(parser.NodeIdentifierAccessSource) if !found { break } currentPath = source } return strings.Join(resolvePathPieces, ".") }
// scopeDynamicMemberAccessExpression scopes a dynamic member access expression in the SRG. func (sb *scopeBuilder) scopeDynamicMemberAccessExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeMemberAccessChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } memberName := node.Get(parser.NodeMemberAccessIdentifier) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) scopeMemberAccess := func(childType typegraph.TypeReference, expectStatic bool) proto.ScopeInfo { // If the child type is any, then this operator returns another value of any, regardless of name. if childType.IsAny() { return newScope().Valid().Resolving(sb.sg.tdg.AnyTypeReference()).GetScope() } var lookupType = childType if childType.IsNullable() { lookupType = childType.AsNonNullable() } // Look for the matching type member, either instance or static. If not found, then the access // returns an "any" type. typeMember, rerr := lookupType.ResolveAccessibleMember(memberName, module, typegraph.MemberResolutionInstanceOrStatic) if rerr != nil { return newScope().Valid().Resolving(sb.sg.tdg.AnyTypeReference()).GetScope() } // Ensure static isn't accessed under instance and vice versa. if typeMember.IsStatic() != expectStatic { if typeMember.IsStatic() { sb.decorateWithError(node, "Member '%v' is static but accessed under an instance value", typeMember.Name()) } else { sb.decorateWithError(node, "Member '%v' is non-static but accessed under a static value", typeMember.Name()) } return newScope().Invalid().GetScope() } // The resulting type (if matching a named scope) is the named scope, but also nullable (since the operator) // allows for nullable types. memberScope := sb.getNamedScopeForMember(typeMember) context.staticDependencyCollector.checkNamedScopeForDependency(memberScope) if childType.IsNullable() { sb.decorateWithWarning(node, "Dynamic access of known member '%v' under type %v. The ?. operator is suggested.", typeMember.Name(), childType) return newScope().ForNamedScopeUnderModifiedType(memberScope, lookupType, makeNullable, context).GetScope() } else { sb.decorateWithWarning(node, "Dynamic access of known member '%v' under type %v. The . operator is suggested.", typeMember.Name(), childType) return newScope().ForNamedScopeUnderType(memberScope, lookupType, context).GetScope() } } switch childScope.GetKind() { case proto.ScopeKind_VALUE: childType := childScope.ResolvedTypeRef(sb.sg.tdg) return scopeMemberAccess(childType, false) case proto.ScopeKind_STATIC: namedScope, _ := sb.getNamedScopeForScope(childScope) if !namedScope.IsType() { sb.decorateWithError(node, "Cannot attempt dynamic member access of '%v' under %v %v, as it is not a type", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() } childType := namedScope.StaticType(context) return scopeMemberAccess(childType, true) case proto.ScopeKind_GENERIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt dynamic member access of '%v' under %v %v, as it is generic without specification", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() default: panic("Unknown scope kind") } return newScope().Invalid().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() }
// salForNode returns a SourceAndLocation for the given graph node. func salForNode(node compilergraph.GraphNode) compilercommon.SourceAndLocation { return salForValues(node.Get(parser.NodePredicateSource), node.Get(parser.NodePredicateStartRune)) }
// salForNode returns a SourceAndLocation for the given graph node. func salForNode(node compilergraph.GraphNode) compilercommon.SourceAndLocation { return compilercommon.NewSourceAndLocation( compilercommon.InputSource(node.Get(parser.NodePredicateSource)), node.GetValue(parser.NodePredicateStartRune).Int()) }
// buildMemberAccessExpression builds the CodeDOM for a member access expression. func (db *domBuilder) buildMemberAccessExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := node.GetNode(parser.NodeMemberAccessChildExpr) return db.buildNamedAccess(node, node.Get(parser.NodeMemberAccessIdentifier), &childExpr) }
// findAddedNameInScope finds the {parameter, with, loop, var} node exposing the given name, if any. func (g *SRG) findAddedNameInScope(name string, node compilergraph.GraphNode) (compilergraph.GraphNode, bool) { nodeSource := node.Get(parser.NodePredicateSource) nodeStartIndex := node.GetValue(parser.NodePredicateStartRune).Int() // Note: This filter ensures that the name is accessible in the scope of the given node by checking that // the node adding the name contains the given node. containingFilter := func(q compilergraph.GraphQuery) compilergraph.Query { startRune := node.GetValue(parser.NodePredicateStartRune).Int() endRune := node.GetValue(parser.NodePredicateEndRune).Int() return q. In(parser.NodePredicateTypeMemberParameter, parser.NodeLambdaExpressionInferredParameter, parser.NodeLambdaExpressionParameter, parser.NodePredicateTypeMemberGeneric, parser.NodeStatementNamedValue, parser.NodeAssignedDestination, parser.NodeAssignedRejection, parser.NodePredicateChild, parser.NodeStatementBlockStatement). InIfKind(parser.NodeStatementBlockStatement, parser.NodeTypeResolveStatement). HasWhere(parser.NodePredicateStartRune, compilergraph.WhereLTE, startRune). HasWhere(parser.NodePredicateEndRune, compilergraph.WhereGTE, endRune) } nit := g.layer.StartQuery(name). In("named"). Has(parser.NodePredicateSource, nodeSource). IsKind(parser.NodeTypeParameter, parser.NodeTypeNamedValue, parser.NodeTypeAssignedValue, parser.NodeTypeVariableStatement, parser.NodeTypeLambdaParameter, parser.NodeTypeGeneric). FilterBy(containingFilter). BuildNodeIterator(parser.NodePredicateStartRune, parser.NodePredicateEndRune) // Sort the nodes found by location and choose the closest node. var results = make(scopeResultNodes, 0) for nit.Next() { node := nit.Node() startIndex := nit.GetPredicate(parser.NodePredicateStartRune).Int() // If the node is a variable statement or assigned value, we have do to additional checks // (since they are not block scoped but rather statement scoped). if node.Kind() == parser.NodeTypeVariableStatement || node.Kind() == parser.NodeTypeAssignedValue { endIndex := nit.GetPredicate(parser.NodePredicateEndRune).Int() if node.Kind() == parser.NodeTypeAssignedValue { if parentNode, ok := node.TryGetIncomingNode(parser.NodeAssignedDestination); ok { endIndex = parentNode.GetValue(parser.NodePredicateEndRune).Int() } else if parentNode, ok := node.TryGetIncomingNode(parser.NodeAssignedRejection); ok { endIndex = parentNode.GetValue(parser.NodePredicateEndRune).Int() } else { panic("Missing assigned parent") } } // Check that the startIndex of the variable statement is <= the startIndex of the parent node if startIndex > nodeStartIndex { continue } // Ensure that the scope starts after the end index of the variable. Otherwise, the variable // name could be used in its initializer expression (which is expressly disallowed). if nodeStartIndex <= endIndex { continue } } results = append(results, scopeResultNode{node, startIndex}) } if len(results) == 1 { // If there is a single result, return it. return results[0].node, true } else if len(results) > 1 { // Otherwise, sort the list by startIndex and choose the one closest to the scope node. sort.Sort(results) return results[0].node, true } return compilergraph.GraphNode{}, false }
// buildIdentifierExpression builds the CodeDOM for an identifier expression. func (db *domBuilder) buildIdentifierExpression(node compilergraph.GraphNode) codedom.Expression { return db.buildNamedAccess(node, node.Get(parser.NodeIdentifierExpressionName), nil) }
// buildBooleanLiteral builds the CodeDOM for a boolean literal. func (db *domBuilder) buildBooleanLiteral(node compilergraph.GraphNode) codedom.Expression { booleanValueStr := node.Get(parser.NodeBooleanLiteralExpressionValue) return codedom.NominalWrapping(codedom.LiteralValue(booleanValueStr, node), db.scopegraph.TypeGraph().BoolType(), node) }