diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2024-10-29 14:49:26 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-29 14:49:26 +0800 |
| commit | f65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch) | |
| tree | ea1d61342cd29368e19135000ec2948813096205 /source/slang/slang-check-conversion.cpp | |
| parent | a729c15e9dce9f5116a38afc66329ab2ca4cea54 (diff) | |
format
* format
* Minor test fixes
* enable checking cpp format in ci
Diffstat (limited to 'source/slang/slang-check-conversion.cpp')
| -rw-r--r-- | source/slang/slang-check-conversion.cpp | 2429 |
1 files changed, 1208 insertions, 1221 deletions
diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index 9ca96ee49..7e33158e6 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -12,259 +12,310 @@ namespace Slang { - ConversionCost SemanticsVisitor::getImplicitConversionCost( - Decl* decl) +ConversionCost SemanticsVisitor::getImplicitConversionCost(Decl* decl) +{ + if (auto modifier = decl->findModifier<ImplicitConversionModifier>()) { - if(auto modifier = decl->findModifier<ImplicitConversionModifier>()) - { - return modifier->cost; - } - - return kConversionCost_Explicit; + return modifier->cost; } - BuiltinConversionKind SemanticsVisitor::getImplicitConversionBuiltinKind( - Decl* decl) - { - if (auto modifier = decl->findModifier<ImplicitConversionModifier>()) - { - return modifier->builtinConversionKind; - } - - return kBuiltinConversion_Unknown; - } + return kConversionCost_Explicit; +} - bool SemanticsVisitor::isEffectivelyScalarForInitializerLists( - Type* type) +BuiltinConversionKind SemanticsVisitor::getImplicitConversionBuiltinKind(Decl* decl) +{ + if (auto modifier = decl->findModifier<ImplicitConversionModifier>()) { - if(as<ArrayExpressionType>(type)) return false; - if(as<VectorExpressionType>(type)) return false; - if(as<MatrixExpressionType>(type)) return false; - - if(as<BasicExpressionType>(type)) - { - return true; - } + return modifier->builtinConversionKind; + } - if(as<ResourceType>(type)) - { - return true; - } - if(as<UntypedBufferResourceType>(type)) - { - return true; - } - if(as<SamplerStateType>(type)) - { - return true; - } + return kBuiltinConversion_Unknown; +} - if(auto declRefType = as<DeclRefType>(type)) - { - if(as<StructDecl>(declRefType->getDeclRef())) - return false; - } +bool SemanticsVisitor::isEffectivelyScalarForInitializerLists(Type* type) +{ + if (as<ArrayExpressionType>(type)) + return false; + if (as<VectorExpressionType>(type)) + return false; + if (as<MatrixExpressionType>(type)) + return false; + if (as<BasicExpressionType>(type)) + { return true; } - bool SemanticsVisitor::shouldUseInitializerDirectly( - Type* toType, - Expr* fromExpr) + if (as<ResourceType>(type)) { - // A nested initializer list should always be used directly. - // - if(as<InitializerListExpr>(fromExpr)) - { - return true; - } - - // If the desired type is a scalar, then we should always initialize - // directly, since it isn't an aggregate. - // - if(isEffectivelyScalarForInitializerLists(toType)) - return true; + return true; + } + if (as<UntypedBufferResourceType>(type)) + { + return true; + } + if (as<SamplerStateType>(type)) + { + return true; + } - // If the type we are initializing isn't effectively scalar, - // but the initialization expression *is*, then it doesn't - // seem like direct initialization is intended. - // - if(isEffectivelyScalarForInitializerLists(fromExpr->type)) + if (auto declRefType = as<DeclRefType>(type)) + { + if (as<StructDecl>(declRefType->getDeclRef())) return false; - - // Once the above cases are handled, the main thing - // we want to check for is whether a direct initialization - // is possible (a type conversion exists). - // - return canCoerce(toType, fromExpr->type, fromExpr); } - bool SemanticsVisitor::_readValueFromInitializerList( - Type* toType, - Expr** outToExpr, - InitializerListExpr* fromInitializerListExpr, - UInt &ioInitArgIndex) + return true; +} + +bool SemanticsVisitor::shouldUseInitializerDirectly(Type* toType, Expr* fromExpr) +{ + // A nested initializer list should always be used directly. + // + if (as<InitializerListExpr>(fromExpr)) { - // First, we will check if we have run out of arguments - // on the initializer list. - // - UInt initArgCount = fromInitializerListExpr->args.getCount(); - if(ioInitArgIndex >= initArgCount) - { - // If we are at the end of the initializer list, - // then our ability to read an argument depends - // on whether the type we are trying to read - // is default-initializable. - // - // For now, we will just pretend like everything - // is default-initializable and move along. - return true; - } + return true; + } - // Okay, we have at least one initializer list expression, - // so we will look at the next expression and decide - // whether to use it to initialize the desired type - // directly (possibly via casts), or as the first sub-expression - // for aggregate initialization. - // - auto firstInitExpr = fromInitializerListExpr->args[ioInitArgIndex]; - if(shouldUseInitializerDirectly(toType, firstInitExpr)) - { - ioInitArgIndex++; - return _coerce( - CoercionSite::Initializer, - toType, - outToExpr, - firstInitExpr->type, - firstInitExpr, - nullptr); - } + // If the desired type is a scalar, then we should always initialize + // directly, since it isn't an aggregate. + // + if (isEffectivelyScalarForInitializerLists(toType)) + return true; - // If there is somehow an error in one of the initialization - // expressions, then everything could be thrown off and we - // shouldn't keep trying to read arguments. - // - if( IsErrorExpr(firstInitExpr) ) - { - // Stop reading arguments, as if we'd reached - // the end of the list. - // - ioInitArgIndex = initArgCount; - return true; - } + // If the type we are initializing isn't effectively scalar, + // but the initialization expression *is*, then it doesn't + // seem like direct initialization is intended. + // + if (isEffectivelyScalarForInitializerLists(fromExpr->type)) + return false; - // The fallback case is to recursively read the - // type from the same list as an aggregate. + // Once the above cases are handled, the main thing + // we want to check for is whether a direct initialization + // is possible (a type conversion exists). + // + return canCoerce(toType, fromExpr->type, fromExpr); +} + +bool SemanticsVisitor::_readValueFromInitializerList( + Type* toType, + Expr** outToExpr, + InitializerListExpr* fromInitializerListExpr, + UInt& ioInitArgIndex) +{ + // First, we will check if we have run out of arguments + // on the initializer list. + // + UInt initArgCount = fromInitializerListExpr->args.getCount(); + if (ioInitArgIndex >= initArgCount) + { + // If we are at the end of the initializer list, + // then our ability to read an argument depends + // on whether the type we are trying to read + // is default-initializable. // - return _readAggregateValueFromInitializerList( + // For now, we will just pretend like everything + // is default-initializable and move along. + return true; + } + + // Okay, we have at least one initializer list expression, + // so we will look at the next expression and decide + // whether to use it to initialize the desired type + // directly (possibly via casts), or as the first sub-expression + // for aggregate initialization. + // + auto firstInitExpr = fromInitializerListExpr->args[ioInitArgIndex]; + if (shouldUseInitializerDirectly(toType, firstInitExpr)) + { + ioInitArgIndex++; + return _coerce( + CoercionSite::Initializer, toType, outToExpr, - fromInitializerListExpr, - ioInitArgIndex); + firstInitExpr->type, + firstInitExpr, + nullptr); } - DeclRefType* findBaseStructType(ASTBuilder* astBuilder, DeclRef<StructDecl> structTypeDeclRef) + // If there is somehow an error in one of the initialization + // expressions, then everything could be thrown off and we + // shouldn't keep trying to read arguments. + // + if (IsErrorExpr(firstInitExpr)) { - auto inheritanceDecl = getMembersOfType<InheritanceDecl>(astBuilder, structTypeDeclRef).getFirstOrNull(); - if(!inheritanceDecl) - return nullptr; - - auto baseType = getBaseType(astBuilder, inheritanceDecl); - auto baseDeclRefType = as<DeclRefType>(baseType); - if(!baseDeclRefType) - return nullptr; + // Stop reading arguments, as if we'd reached + // the end of the list. + // + ioInitArgIndex = initArgCount; + return true; + } - auto baseDeclRef = baseDeclRefType->getDeclRef(); - auto baseStructDeclRef = baseDeclRef.as<StructDecl>(); - if(!baseStructDeclRef) - return nullptr; + // The fallback case is to recursively read the + // type from the same list as an aggregate. + // + return _readAggregateValueFromInitializerList( + toType, + outToExpr, + fromInitializerListExpr, + ioInitArgIndex); +} - return baseDeclRefType; - } +DeclRefType* findBaseStructType(ASTBuilder* astBuilder, DeclRef<StructDecl> structTypeDeclRef) +{ + auto inheritanceDecl = + getMembersOfType<InheritanceDecl>(astBuilder, structTypeDeclRef).getFirstOrNull(); + if (!inheritanceDecl) + return nullptr; + + auto baseType = getBaseType(astBuilder, inheritanceDecl); + auto baseDeclRefType = as<DeclRefType>(baseType); + if (!baseDeclRefType) + return nullptr; + + auto baseDeclRef = baseDeclRefType->getDeclRef(); + auto baseStructDeclRef = baseDeclRef.as<StructDecl>(); + if (!baseStructDeclRef) + return nullptr; + + return baseDeclRefType; +} - DeclRef<StructDecl> findBaseStructDeclRef(ASTBuilder* astBuilder, DeclRef<StructDecl> structTypeDeclRef) - { - auto inheritanceDecl = getMembersOfType<InheritanceDecl>(astBuilder, structTypeDeclRef).getFirstOrNull(); - if (!inheritanceDecl) - return DeclRef<StructDecl>(); +DeclRef<StructDecl> findBaseStructDeclRef( + ASTBuilder* astBuilder, + DeclRef<StructDecl> structTypeDeclRef) +{ + auto inheritanceDecl = + getMembersOfType<InheritanceDecl>(astBuilder, structTypeDeclRef).getFirstOrNull(); + if (!inheritanceDecl) + return DeclRef<StructDecl>(); + + auto baseType = getBaseType(astBuilder, inheritanceDecl); + auto baseDeclRefType = as<DeclRefType>(baseType); + if (!baseDeclRefType) + return DeclRef<StructDecl>(); + + auto baseDeclRef = baseDeclRefType->getDeclRef(); + auto baseStructDeclRef = baseDeclRef.as<StructDecl>(); + if (!baseStructDeclRef) + return DeclRef<StructDecl>(); + + return baseStructDeclRef; +} - auto baseType = getBaseType(astBuilder, inheritanceDecl); - auto baseDeclRefType = as<DeclRefType>(baseType); - if (!baseDeclRefType) - return DeclRef<StructDecl>(); +bool SemanticsVisitor::_readAggregateValueFromInitializerList( + Type* inToType, + Expr** outToExpr, + InitializerListExpr* fromInitializerListExpr, + UInt& ioArgIndex) +{ + auto toType = inToType; + UInt argCount = fromInitializerListExpr->args.getCount(); - auto baseDeclRef = baseDeclRefType->getDeclRef(); - auto baseStructDeclRef = baseDeclRef.as<StructDecl>(); - if (!baseStructDeclRef) - return DeclRef<StructDecl>(); + // In the case where we need to build a result expression, + // we will collect the new arguments here + List<Expr*> coercedArgs; - return baseStructDeclRef; + if (isEffectivelyScalarForInitializerLists(toType)) + { + // For any type that is effectively a non-aggregate, + // we expect to read a single value from the initializer list + // + if (ioArgIndex < argCount) + { + auto arg = fromInitializerListExpr->args[ioArgIndex++]; + return _coerce(CoercionSite::Initializer, toType, outToExpr, arg->type, arg, nullptr); + } + else + { + // If there wasn't an initialization + // expression to be found, then we need + // to perform default initialization here. + // + // We will let this case come through the front-end + // as an `InitializerListExpr` with zero arguments, + // and then have the IR generation logic deal with + // synthesizing default values. + } } - - bool SemanticsVisitor::_readAggregateValueFromInitializerList( - Type* inToType, - Expr** outToExpr, - InitializerListExpr* fromInitializerListExpr, - UInt &ioArgIndex) + else if (auto toVecType = as<VectorExpressionType>(toType)) { - auto toType = inToType; - UInt argCount = fromInitializerListExpr->args.getCount(); - - // In the case where we need to build a result expression, - // we will collect the new arguments here - List<Expr*> coercedArgs; + auto toElementCount = toVecType->getElementCount(); + auto toElementType = toVecType->getElementType(); - if(isEffectivelyScalarForInitializerLists(toType)) + UInt elementCount = 0; + if (auto constElementCount = as<ConstantIntVal>(toElementCount)) { - // For any type that is effectively a non-aggregate, - // we expect to read a single value from the initializer list + elementCount = (UInt)constElementCount->getValue(); + } + else + { + // We don't know the element count statically, + // so what are we supposed to be doing? // - if(ioArgIndex < argCount) + if (outToExpr) { - auto arg = fromInitializerListExpr->args[ioArgIndex++]; - return _coerce( - CoercionSite::Initializer, - toType, - outToExpr, - arg->type, - arg, - nullptr); + getSink()->diagnose( + fromInitializerListExpr, + Diagnostics::cannotUseInitializerListForVectorOfUnknownSize, + toElementCount); } - else + return false; + } + + for (UInt ee = 0; ee < elementCount; ++ee) + { + Expr* coercedArg = nullptr; + bool argResult = _readValueFromInitializerList( + toElementType, + outToExpr ? &coercedArg : nullptr, + fromInitializerListExpr, + ioArgIndex); + + // No point in trying further if any argument fails + if (!argResult) + return false; + + if (coercedArg) { - // If there wasn't an initialization - // expression to be found, then we need - // to perform default initialization here. - // - // We will let this case come through the front-end - // as an `InitializerListExpr` with zero arguments, - // and then have the IR generation logic deal with - // synthesizing default values. + coercedArgs.add(coercedArg); } } - else if (auto toVecType = as<VectorExpressionType>(toType)) + } + else if (auto toArrayType = as<ArrayExpressionType>(toType)) + { + // TODO(tfoley): If we can compute the size of the array statically, + // then we want to check that there aren't too many initializers present + + auto toElementType = toArrayType->getElementType(); + if (!toArrayType->isUnsized()) { - auto toElementCount = toVecType->getElementCount(); - auto toElementType = toVecType->getElementType(); + auto toElementCount = toArrayType->getElementCount(); + // In the case of a sized array, we need to check that the number + // of elements being initialized matches what was declared. + // UInt elementCount = 0; if (auto constElementCount = as<ConstantIntVal>(toElementCount)) { - elementCount = (UInt) constElementCount->getValue(); + elementCount = (UInt)constElementCount->getValue(); } else { // We don't know the element count statically, // so what are we supposed to be doing? // - if(outToExpr) + if (outToExpr) { - getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForVectorOfUnknownSize, toElementCount); + getSink()->diagnose( + fromInitializerListExpr, + Diagnostics::cannotUseInitializerListForArrayOfUnknownSize, + toElementCount); } return false; } - for(UInt ee = 0; ee < elementCount; ++ee) + for (UInt ee = 0; ee < elementCount; ++ee) { Expr* coercedArg = nullptr; bool argResult = _readValueFromInitializerList( @@ -274,1300 +325,1236 @@ namespace Slang ioArgIndex); // No point in trying further if any argument fails - if(!argResult) + if (!argResult) return false; - if( coercedArg ) + if (coercedArg) { coercedArgs.add(coercedArg); } } } - else if(auto toArrayType = as<ArrayExpressionType>(toType)) + else { - // TODO(tfoley): If we can compute the size of the array statically, - // then we want to check that there aren't too many initializers present - - auto toElementType = toArrayType->getElementType(); - if(!toArrayType->isUnsized()) + // In the case of an unsized array type, we will use the + // number of arguments to the initializer to determine + // the element count. + // + UInt elementCount = 0; + while (ioArgIndex < argCount) { - auto toElementCount = toArrayType->getElementCount(); + Expr* coercedArg = nullptr; + bool argResult = _readValueFromInitializerList( + toElementType, + outToExpr ? &coercedArg : nullptr, + fromInitializerListExpr, + ioArgIndex); - // In the case of a sized array, we need to check that the number - // of elements being initialized matches what was declared. - // - UInt elementCount = 0; - if (auto constElementCount = as<ConstantIntVal>(toElementCount)) - { - elementCount = (UInt) constElementCount->getValue(); - } - else - { - // We don't know the element count statically, - // so what are we supposed to be doing? - // - if(outToExpr) - { - getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForArrayOfUnknownSize, toElementCount); - } + // No point in trying further if any argument fails + if (!argResult) return false; - } - for(UInt ee = 0; ee < elementCount; ++ee) - { - Expr* coercedArg = nullptr; - bool argResult = _readValueFromInitializerList( - toElementType, - outToExpr ? &coercedArg : nullptr, - fromInitializerListExpr, - ioArgIndex); - - // No point in trying further if any argument fails - if(!argResult) - return false; + elementCount++; - if( coercedArg ) - { - coercedArgs.add(coercedArg); - } + if (coercedArg) + { + coercedArgs.add(coercedArg); } } - else - { - // In the case of an unsized array type, we will use the - // number of arguments to the initializer to determine - // the element count. - // - UInt elementCount = 0; - while(ioArgIndex < argCount) - { - Expr* coercedArg = nullptr; - bool argResult = _readValueFromInitializerList( - toElementType, - outToExpr ? &coercedArg : nullptr, - fromInitializerListExpr, - ioArgIndex); - // No point in trying further if any argument fails - if(!argResult) - return false; + // We have a new type for the conversion, based on what + // we learned. + toType = m_astBuilder->getArrayType( + toElementType, + m_astBuilder->getIntVal(m_astBuilder->getIntType(), elementCount)); + } + } + else if (auto toMatrixType = as<MatrixExpressionType>(toType)) + { + // In the general case, the initializer list might comprise + // both vectors and scalars. + // + // The traditional HLSL compilers treat any vectors in + // the initializer list exactly equivalent to their sequence + // of scalar elements, and don't care how this might, or + // might not, align with the rows of the matrix. + // + // We will draw a line in the sand and say that an initializer + // list for a matrix will act as if the matrix type were an + // array of vectors for the rows. - elementCount++; - if( coercedArg ) - { - coercedArgs.add(coercedArg); - } - } + UInt rowCount = 0; + auto toRowType = + createVectorType(toMatrixType->getElementType(), toMatrixType->getColumnCount()); - // We have a new type for the conversion, based on what - // we learned. - toType = m_astBuilder->getArrayType(toElementType, - m_astBuilder->getIntVal(m_astBuilder->getIntType(), elementCount)); - } + if (auto constRowCount = as<ConstantIntVal>(toMatrixType->getRowCount())) + { + rowCount = (UInt)constRowCount->getValue(); } - else if(auto toMatrixType = as<MatrixExpressionType>(toType)) + else { - // In the general case, the initializer list might comprise - // both vectors and scalars. - // - // The traditional HLSL compilers treat any vectors in - // the initializer list exactly equivalent to their sequence - // of scalar elements, and don't care how this might, or - // might not, align with the rows of the matrix. + // We don't know the element count statically, + // so what are we supposed to be doing? // - // We will draw a line in the sand and say that an initializer - // list for a matrix will act as if the matrix type were an - // array of vectors for the rows. - - - UInt rowCount = 0; - auto toRowType = createVectorType( - toMatrixType->getElementType(), - toMatrixType->getColumnCount()); - - if (auto constRowCount = as<ConstantIntVal>(toMatrixType->getRowCount())) + if (outToExpr) { - rowCount = (UInt) constRowCount->getValue(); + getSink()->diagnose( + fromInitializerListExpr, + Diagnostics::cannotUseInitializerListForMatrixOfUnknownSize, + toMatrixType->getRowCount()); } - else - { - // We don't know the element count statically, - // so what are we supposed to be doing? - // - if(outToExpr) - { - getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForMatrixOfUnknownSize, toMatrixType->getRowCount()); - } + return false; + } + + for (UInt rr = 0; rr < rowCount; ++rr) + { + Expr* coercedArg = nullptr; + bool argResult = _readValueFromInitializerList( + toRowType, + outToExpr ? &coercedArg : nullptr, + fromInitializerListExpr, + ioArgIndex); + + // No point in trying further if any argument fails + if (!argResult) return false; - } - for(UInt rr = 0; rr < rowCount; ++rr) + if (coercedArg) + { + coercedArgs.add(coercedArg); + } + } + } + else if (auto toDeclRefType = as<DeclRefType>(toType)) + { + auto toTypeDeclRef = toDeclRefType->getDeclRef(); + if (auto toStructDeclRef = toTypeDeclRef.as<StructDecl>()) + { + // Trying to initialize a `struct` type given an initializer list. + // + // Before we iterate over the fields, we want to check if this struct + // inherits from another `struct` type. If so, we want to read + // an initializer for that base type first. + // + if (auto baseStructType = findBaseStructType(m_astBuilder, toStructDeclRef)) { Expr* coercedArg = nullptr; bool argResult = _readValueFromInitializerList( - toRowType, + baseStructType, outToExpr ? &coercedArg : nullptr, fromInitializerListExpr, ioArgIndex); // No point in trying further if any argument fails - if(!argResult) + if (!argResult) return false; - if( coercedArg ) + if (coercedArg) { coercedArgs.add(coercedArg); } } - } - else if(auto toDeclRefType = as<DeclRefType>(toType)) - { - auto toTypeDeclRef = toDeclRefType->getDeclRef(); - if(auto toStructDeclRef = toTypeDeclRef.as<StructDecl>()) - { - // Trying to initialize a `struct` type given an initializer list. - // - // Before we iterate over the fields, we want to check if this struct - // inherits from another `struct` type. If so, we want to read - // an initializer for that base type first. - // - if (auto baseStructType = findBaseStructType(m_astBuilder, toStructDeclRef)) - { - Expr* coercedArg = nullptr; - bool argResult = _readValueFromInitializerList( - baseStructType, - outToExpr ? &coercedArg : nullptr, - fromInitializerListExpr, - ioArgIndex); - // No point in trying further if any argument fails - if (!argResult) - return false; + // We will go through the fields in order and try to match them + // up with initializer arguments. + // + for (auto fieldDeclRef : getMembersOfType<VarDecl>( + m_astBuilder, + toStructDeclRef, + MemberFilterStyle::Instance)) + { + Expr* coercedArg = nullptr; + bool argResult = _readValueFromInitializerList( + getType(m_astBuilder, fieldDeclRef), + outToExpr ? &coercedArg : nullptr, + fromInitializerListExpr, + ioArgIndex); - if (coercedArg) - { - coercedArgs.add(coercedArg); - } - } + // No point in trying further if any argument fails + if (!argResult) + return false; - // We will go through the fields in order and try to match them - // up with initializer arguments. - // - for(auto fieldDeclRef : getMembersOfType<VarDecl>(m_astBuilder, toStructDeclRef, MemberFilterStyle::Instance)) + if (coercedArg) { - Expr* coercedArg = nullptr; - bool argResult = _readValueFromInitializerList( - getType(m_astBuilder, fieldDeclRef), - outToExpr ? &coercedArg : nullptr, - fromInitializerListExpr, - ioArgIndex); - - // No point in trying further if any argument fails - if(!argResult) - return false; - - if( coercedArg ) - { - coercedArgs.add(coercedArg); - } + coercedArgs.add(coercedArg); } } } - else + } + else + { + // We shouldn't get to this case in practice, + // but just in case we'll consider an initializer + // list invalid if we are trying to read something + // off of it that wasn't handled by the cases above. + // + if (outToExpr) { - // We shouldn't get to this case in practice, - // but just in case we'll consider an initializer - // list invalid if we are trying to read something - // off of it that wasn't handled by the cases above. - // - if(outToExpr) - { - getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForType, inToType); - } - return false; + getSink()->diagnose( + fromInitializerListExpr, + Diagnostics::cannotUseInitializerListForType, + inToType); } + return false; + } + + // We were able to coerce all the arguments given, and so + // we need to construct a suitable expression to remember the result + // + if (outToExpr) + { + auto toInitializerListExpr = m_astBuilder->create<InitializerListExpr>(); + toInitializerListExpr->loc = fromInitializerListExpr->loc; + toInitializerListExpr->type = QualType(toType); + toInitializerListExpr->args = coercedArgs; - // We were able to coerce all the arguments given, and so - // we need to construct a suitable expression to remember the result + // Wrap initalizer list args if we're creating a non-differentiable struct within a + // differentiable function. // - if(outToExpr) + if (auto func = getParentFuncOfVisitor()) { - auto toInitializerListExpr = m_astBuilder->create<InitializerListExpr>(); - toInitializerListExpr->loc = fromInitializerListExpr->loc; - toInitializerListExpr->type = QualType(toType); - toInitializerListExpr->args = coercedArgs; - - // Wrap initalizer list args if we're creating a non-differentiable struct within a - // differentiable function. - // - if (auto func = getParentFuncOfVisitor()) + if (func->findModifier<DifferentiableAttribute>() && !isTypeDifferentiable(toType)) { - if (func->findModifier<DifferentiableAttribute>() && - !isTypeDifferentiable(toType)) + for (auto& arg : toInitializerListExpr->args) { - for (auto &arg : toInitializerListExpr->args) + if (isTypeDifferentiable(arg->type.type)) { - if (isTypeDifferentiable(arg->type.type)) - { - auto detachedArg = m_astBuilder->create<DetachExpr>(); - detachedArg->inner = arg; - detachedArg->type = arg->type; - arg = detachedArg; - } + auto detachedArg = m_astBuilder->create<DetachExpr>(); + detachedArg->inner = arg; + detachedArg->type = arg->type; + arg = detachedArg; } } } - - *outToExpr = toInitializerListExpr; } - return true; + *outToExpr = toInitializerListExpr; } - bool SemanticsVisitor::_coerceInitializerList( - Type* toType, - Expr** outToExpr, - InitializerListExpr* fromInitializerListExpr) - { - UInt argCount = fromInitializerListExpr->args.getCount(); - UInt argIndex = 0; - - // TODO: we should handle the special case of `{0}` as an initializer - // for arbitrary `struct` types here. - - // If this initializer list has a more specific type than just - // InitializerListType (i.e. it's already undergone a coercion) we - // should ensure that we're allowed to coerce from that type to our - // desired type. - // If this isn't prohibited, then we can proceed to try and coerce from - // the initializer list itself; assuming that coercion is closed under - // composition this shouldn't fail. - if(!as<InitializerListType>(fromInitializerListExpr->type) && - !canCoerce(toType, fromInitializerListExpr->type, nullptr)) - return _failedCoercion(toType, outToExpr, fromInitializerListExpr); - - if(!_readAggregateValueFromInitializerList(toType, outToExpr, fromInitializerListExpr, argIndex)) - return false; + return true; +} + +bool SemanticsVisitor::_coerceInitializerList( + Type* toType, + Expr** outToExpr, + InitializerListExpr* fromInitializerListExpr) +{ + UInt argCount = fromInitializerListExpr->args.getCount(); + UInt argIndex = 0; + + // TODO: we should handle the special case of `{0}` as an initializer + // for arbitrary `struct` types here. + + // If this initializer list has a more specific type than just + // InitializerListType (i.e. it's already undergone a coercion) we + // should ensure that we're allowed to coerce from that type to our + // desired type. + // If this isn't prohibited, then we can proceed to try and coerce from + // the initializer list itself; assuming that coercion is closed under + // composition this shouldn't fail. + if (!as<InitializerListType>(fromInitializerListExpr->type) && + !canCoerce(toType, fromInitializerListExpr->type, nullptr)) + return _failedCoercion(toType, outToExpr, fromInitializerListExpr); + + if (!_readAggregateValueFromInitializerList( + toType, + outToExpr, + fromInitializerListExpr, + argIndex)) + return false; - if(argIndex != argCount) + if (argIndex != argCount) + { + if (outToExpr) { - if( outToExpr ) - { - getSink()->diagnose(fromInitializerListExpr, Diagnostics::tooManyInitializers, argIndex, argCount); - } + getSink()->diagnose( + fromInitializerListExpr, + Diagnostics::tooManyInitializers, + argIndex, + argCount); } - - return true; } - bool SemanticsVisitor::_failedCoercion( - Type* toType, - Expr** outToExpr, - Expr* fromExpr) + return true; +} + +bool SemanticsVisitor::_failedCoercion(Type* toType, Expr** outToExpr, Expr* fromExpr) +{ + if (outToExpr) { - if(outToExpr) + // As a special case, if the expression we are trying to convert + // from is overloaded (implying an ambiguous reference), then we + // will try to produce a more appropriately tailored error message. + // + auto fromType = fromExpr->type.type; + if (as<OverloadGroupType>(fromType)) { - // As a special case, if the expression we are trying to convert - // from is overloaded (implying an ambiguous reference), then we - // will try to produce a more appropriately tailored error message. - // - auto fromType = fromExpr->type.type; - if( as<OverloadGroupType>(fromType) ) - { - diagnoseAmbiguousReference(fromExpr); - } - else - { - getSink()->diagnose(fromExpr->loc, Diagnostics::typeMismatch, toType, fromExpr->type); - } + diagnoseAmbiguousReference(fromExpr); + } + else + { + getSink()->diagnose(fromExpr->loc, Diagnostics::typeMismatch, toType, fromExpr->type); } - return false; } + return false; +} - /// Do the `left` and `right` modifiers represent the same thing? - static bool _doModifiersMatch(Val* left, Val* right) - { - if( left == right ) - return true; +/// Do the `left` and `right` modifiers represent the same thing? +static bool _doModifiersMatch(Val* left, Val* right) +{ + if (left == right) + return true; - if( left->equals(right) ) - return true; + if (left->equals(right)) + return true; + return false; +} + +/// Does `type` have a modifier that matches `modifier`? +static bool _hasMatchingModifier(ModifiedType* type, Val* modifier) +{ + if (!type) return false; - } - /// Does `type` have a modifier that matches `modifier`? - static bool _hasMatchingModifier(ModifiedType* type, Val* modifier) + for (Index m = 0; m < type->getModifierCount(); m++) { - if(!type) return false; - - for (Index m = 0; m < type->getModifierCount(); m++) - { - if(_doModifiersMatch(type->getModifier(m), modifier)) - return true; - } - - return false; + if (_doModifiersMatch(type->getModifier(m), modifier)) + return true; } - /// Can `modifier` be added to a type as part of a coercion? - /// - /// For example, it is generally safe to convert from a value - /// of type `T` to a value of type `const T` in C/C++. - /// - static bool _canModifierBeAddedDuringCoercion(Val* modifier) + return false; +} + +/// Can `modifier` be added to a type as part of a coercion? +/// +/// For example, it is generally safe to convert from a value +/// of type `T` to a value of type `const T` in C/C++. +/// +static bool _canModifierBeAddedDuringCoercion(Val* modifier) +{ + switch (modifier->astNodeType) { - switch( modifier->astNodeType ) - { - default: - return false; + default: return false; - case ASTNodeType::UNormModifierVal: - case ASTNodeType::SNormModifierVal: - case ASTNodeType::NoDiffModifierVal: - return true; - } + case ASTNodeType::UNormModifierVal: + case ASTNodeType::SNormModifierVal: + case ASTNodeType::NoDiffModifierVal: return true; } +} - /// Can `modifier` be dropped from a type as part of a coercion? - /// - /// For example, it is generally safe to convert from a value - /// of type `const T` to a value of type `T` in C/C++. - /// - static bool _canModifierBeDroppedDuringCoercion(Val* modifier) +/// Can `modifier` be dropped from a type as part of a coercion? +/// +/// For example, it is generally safe to convert from a value +/// of type `const T` to a value of type `T` in C/C++. +/// +static bool _canModifierBeDroppedDuringCoercion(Val* modifier) +{ + switch (modifier->astNodeType) { - switch( modifier->astNodeType ) - { - default: - return false; + default: return false; - case ASTNodeType::UNormModifierVal: - case ASTNodeType::SNormModifierVal: - case ASTNodeType::NoDiffModifierVal: - return true; - } + case ASTNodeType::UNormModifierVal: + case ASTNodeType::SNormModifierVal: + case ASTNodeType::NoDiffModifierVal: return true; } +} - static bool isSigned(Type* t) +static bool isSigned(Type* t) +{ + auto basicType = as<BasicExpressionType>(t); + if (!basicType) + return false; + switch (basicType->getBaseType()) { - auto basicType = as<BasicExpressionType>(t); - if (!basicType) return false; - switch (basicType->getBaseType()) - { - case BaseType::Int8: - case BaseType::Int16: - case BaseType::Int: - case BaseType::Int64: - case BaseType::IntPtr: - return true; - default: - return false; - } + case BaseType::Int8: + case BaseType::Int16: + case BaseType::Int: + case BaseType::Int64: + case BaseType::IntPtr: return true; + default: return false; } +} - int getTypeBitSize(Type* t) - { - auto basicType = as<BasicExpressionType>(t); - if (!basicType) return 0; +int getTypeBitSize(Type* t) +{ + auto basicType = as<BasicExpressionType>(t); + if (!basicType) + return 0; - switch (basicType->getBaseType()) - { - case BaseType::Int8: - case BaseType::UInt8: - return 8; - case BaseType::Int16: - case BaseType::UInt16: - return 16; - case BaseType::Int: - case BaseType::UInt: - return 32; - case BaseType::Int64: - case BaseType::UInt64: - return 64; - case BaseType::IntPtr: - case BaseType::UIntPtr: + switch (basicType->getBaseType()) + { + case BaseType::Int8: + case BaseType::UInt8: return 8; + case BaseType::Int16: + case BaseType::UInt16: return 16; + case BaseType::Int: + case BaseType::UInt: return 32; + case BaseType::Int64: + case BaseType::UInt64: return 64; + case BaseType::IntPtr: + case BaseType::UIntPtr: #if SLANG_PTR_IS_32 - return 32; + return 32; #else - return 64; + return 64; #endif - default: - return 0; - } + default: return 0; } +} - ConversionCost SemanticsVisitor::getImplicitConversionCostWithKnownArg(Decl* decl, Type* toType, Expr* arg) - { - ConversionCost candidateCost = getImplicitConversionCost(decl); +ConversionCost SemanticsVisitor::getImplicitConversionCostWithKnownArg( + Decl* decl, + Type* toType, + Expr* arg) +{ + ConversionCost candidateCost = getImplicitConversionCost(decl); - // Fix up the cost if the operand is a const lit. - if (isScalarIntegerType(toType)) + // Fix up the cost if the operand is a const lit. + if (isScalarIntegerType(toType)) + { + auto knownVal = as<IntegerLiteralExpr>(arg); + if (!knownVal) + return candidateCost; + if (getIntValueBitSize(knownVal->value) <= getTypeBitSize(toType)) { - auto knownVal = as<IntegerLiteralExpr>(arg); - if (!knownVal) - return candidateCost; - if (getIntValueBitSize(knownVal->value) <= getTypeBitSize(toType)) - { - bool toTypeIsSigned = isSigned(toType); - bool fromTypeIsSigned = isSigned(knownVal->type); - if (toTypeIsSigned == fromTypeIsSigned) - candidateCost = kConversionCost_InRangeIntLitConversion; - else if (toTypeIsSigned) - candidateCost = kConversionCost_InRangeIntLitUnsignedToSignedConversion; - else - candidateCost = kConversionCost_InRangeIntLitSignedToUnsignedConversion; - } + bool toTypeIsSigned = isSigned(toType); + bool fromTypeIsSigned = isSigned(knownVal->type); + if (toTypeIsSigned == fromTypeIsSigned) + candidateCost = kConversionCost_InRangeIntLitConversion; + else if (toTypeIsSigned) + candidateCost = kConversionCost_InRangeIntLitUnsignedToSignedConversion; + else + candidateCost = kConversionCost_InRangeIntLitSignedToUnsignedConversion; } - return candidateCost; } + return candidateCost; +} - bool SemanticsVisitor::_coerce( - CoercionSite site, - Type* toType, - Expr** outToExpr, - QualType fromType, - Expr* fromExpr, - ConversionCost* outCost) +bool SemanticsVisitor::_coerce( + CoercionSite site, + Type* toType, + Expr** outToExpr, + QualType fromType, + Expr* fromExpr, + ConversionCost* outCost) +{ + // If we are about to try and coerce an overloaded expression, + // then we should start by trying to resolve the ambiguous reference + // based on prioritization of the different candidates. + // + // TODO: A more powerful model would be to try to coerce each + // of the constituent overload candidates, filtering down to + // those that are coercible, and then disambiguating the result. + // Such an approach would let us disambiguate between overloaded + // symbols based on their type (e.g., by casting the name of + // an overloaded function to the type of the overload we mean + // to reference). + // + if (auto fromOverloadedExpr = as<OverloadedExpr>(fromExpr)) { - // If we are about to try and coerce an overloaded expression, - // then we should start by trying to resolve the ambiguous reference - // based on prioritization of the different candidates. - // - // TODO: A more powerful model would be to try to coerce each - // of the constituent overload candidates, filtering down to - // those that are coercible, and then disambiguating the result. - // Such an approach would let us disambiguate between overloaded - // symbols based on their type (e.g., by casting the name of - // an overloaded function to the type of the overload we mean - // to reference). - // - if( auto fromOverloadedExpr = as<OverloadedExpr>(fromExpr) ) - { - auto resolvedExpr = maybeResolveOverloadedExpr(fromOverloadedExpr, LookupMask::Default, nullptr); + auto resolvedExpr = + maybeResolveOverloadedExpr(fromOverloadedExpr, LookupMask::Default, nullptr); - fromExpr = resolvedExpr; - fromType = resolvedExpr->type; - } + fromExpr = resolvedExpr; + fromType = resolvedExpr->type; + } - // An important and easy case is when the "to" and "from" types are equal. - // - if( toType->equals(fromType) ) - { - if(outToExpr) - *outToExpr = fromExpr; - if(outCost) - *outCost = kConversionCost_None; - return true; - } + // An important and easy case is when the "to" and "from" types are equal. + // + if (toType->equals(fromType)) + { + if (outToExpr) + *outToExpr = fromExpr; + if (outCost) + *outCost = kConversionCost_None; + return true; + } - // If both are string types we assume they are convertable in both directions - if (as<StringTypeBase>(fromType) && as<StringTypeBase>(toType)) - { - if (outToExpr) - *outToExpr = fromExpr; - if (outCost) - *outCost = kConversionCost_None; - return true; - } + // If both are string types we assume they are convertable in both directions + if (as<StringTypeBase>(fromType) && as<StringTypeBase>(toType)) + { + if (outToExpr) + *outToExpr = fromExpr; + if (outCost) + *outCost = kConversionCost_None; + return true; + } - // Allow implicit conversion from sized array to unsized array when - // calling a function. - // Note: we implement the logic here instead of an implicit_conversion - // intrinsic in the core module because we only want to allow this conversion - // when calling a function. - // - if (site == CoercionSite::Argument) + // Allow implicit conversion from sized array to unsized array when + // calling a function. + // Note: we implement the logic here instead of an implicit_conversion + // intrinsic in the core module because we only want to allow this conversion + // when calling a function. + // + if (site == CoercionSite::Argument) + { + if (auto fromArrayType = as<ArrayExpressionType>(fromType)) { - if (auto fromArrayType = as<ArrayExpressionType>(fromType)) + if (auto toArrayType = as<ArrayExpressionType>(toType)) { - if (auto toArrayType = as<ArrayExpressionType>(toType)) + if (fromArrayType->getElementType()->equals(toArrayType->getElementType()) && + toArrayType->isUnsized()) { - if (fromArrayType->getElementType()->equals(toArrayType->getElementType()) - && toArrayType->isUnsized()) - { - if (outToExpr) - *outToExpr = fromExpr; - if (outCost) - *outCost = kConversionCost_SizedArrayToUnsizedArray; - return true; - } + if (outToExpr) + *outToExpr = fromExpr; + if (outCost) + *outCost = kConversionCost_SizedArrayToUnsizedArray; + return true; } } } + } + + // Another important case is when either the "to" or "from" type + // represents an error. In such a case we must have already + // reporeted the error, so it is better to allow the conversion + // to pass than to report a "cascading" error that might not + // make any sense. + // + if (as<ErrorType>(toType) || as<ErrorType>(fromType)) + { + if (outToExpr) + *outToExpr = CreateImplicitCastExpr(toType, fromExpr); + if (outCost) + *outCost = kConversionCost_None; + return true; + } - // Another important case is when either the "to" or "from" type - // represents an error. In such a case we must have already - // reporeted the error, so it is better to allow the conversion - // to pass than to report a "cascading" error that might not - // make any sense. + { + // It is possible that one or more of the types involved might have modifiers + // on it, but the underlying types are otherwise the same. // - if(as<ErrorType>(toType) || as<ErrorType>(fromType)) - { - if(outToExpr) - *outToExpr = CreateImplicitCastExpr(toType, fromExpr); - if(outCost) - *outCost = kConversionCost_None; - return true; - } + auto toModified = as<ModifiedType>(toType); + auto toBase = toModified ? toModified->getBase() : toType; + // + auto fromModified = as<ModifiedType>(fromType); + auto fromBase = + fromModified ? QualType(fromModified->getBase(), fromType.isLeftValue) : fromType; + + if ((toModified || fromModified) && toBase->equals(fromBase)) { - // It is possible that one or more of the types involved might have modifiers - // on it, but the underlying types are otherwise the same. + // We need to check each modifier present on either `toType` + // or `fromType`. For each modifier, it will either be: // - auto toModified = as<ModifiedType>(toType); - auto toBase = toModified ? toModified->getBase() : toType; + // * Present on both types; these are a non-issue + // * Present only on `toType` + // * Present only on `fromType` // - auto fromModified = as<ModifiedType>(fromType); - auto fromBase = fromModified ? QualType(fromModified->getBase(), fromType.isLeftValue) : fromType; - - - if((toModified || fromModified) && toBase->equals(fromBase)) + if (toModified) { - // We need to check each modifier present on either `toType` - // or `fromType`. For each modifier, it will either be: - // - // * Present on both types; these are a non-issue - // * Present only on `toType` - // * Present only on `fromType` - // - if( toModified ) + for (Index m = 0; m < toModified->getModifierCount(); m++) { - for (Index m = 0; m < toModified->getModifierCount(); m++) + auto modifier = toModified->getModifier(m); + if (_hasMatchingModifier(fromModified, modifier)) + continue; + + // If `modifier` is present on `toType`, but not `fromType`, + // then we need to know whether this modifier can be added + // to the type of an expression as part of coercion. + // + if (!_canModifierBeAddedDuringCoercion(modifier)) { - auto modifier = toModified->getModifier(m); - if(_hasMatchingModifier(fromModified, modifier)) - continue; - - // If `modifier` is present on `toType`, but not `fromType`, - // then we need to know whether this modifier can be added - // to the type of an expression as part of coercion. - // - if( !_canModifierBeAddedDuringCoercion(modifier) ) - { - return _failedCoercion(toType, outToExpr, fromExpr); - } + return _failedCoercion(toType, outToExpr, fromExpr); } } - if( fromModified ) + } + if (fromModified) + { + for (Index m = 0; m < fromModified->getModifierCount(); m++) { - for (Index m = 0; m < fromModified->getModifierCount(); m++) - { - auto modifier = fromModified->getModifier(m); + auto modifier = fromModified->getModifier(m); - if(_hasMatchingModifier(toModified, modifier)) - continue; + if (_hasMatchingModifier(toModified, modifier)) + continue; - // If `modifier` is present on `fromType`, but not `toType`, - // then we need to know whether this modifier can be dropped - // to the type of an expression as part of coercion. - // - if( !_canModifierBeDroppedDuringCoercion(modifier) ) - { - return _failedCoercion(toType, outToExpr, fromExpr); - } + // If `modifier` is present on `fromType`, but not `toType`, + // then we need to know whether this modifier can be dropped + // to the type of an expression as part of coercion. + // + if (!_canModifierBeDroppedDuringCoercion(modifier)) + { + return _failedCoercion(toType, outToExpr, fromExpr); } } + } - // If all the modifiers were okay, we can convert. + // If all the modifiers were okay, we can convert. - // TODO: we may need a cost to allow disambiguation of overloads based on modifiers? - if(outCost) - { - *outCost = kConversionCost_None; - } - if( outToExpr ) - { - *outToExpr = createModifierCastExpr(toType, fromExpr); - } - - return true; + // TODO: we may need a cost to allow disambiguation of overloads based on modifiers? + if (outCost) + { + *outCost = kConversionCost_None; } - } - - // Coercion from an initializer list is allowed for many types, - // so we will farm that out to its own subroutine. - // - if (fromExpr && as<InitializerListType>(fromExpr->type.type)) - { - if (auto fromInitializerListExpr = as<InitializerListExpr>(fromExpr)) + if (outToExpr) { - if (!_coerceInitializerList( - toType, - outToExpr, - fromInitializerListExpr)) - { - return false; - } - - // For now, we treat coercion from an initializer list - // as having no cost, so that all conversions from initializer - // lists are equally valid. This is fine given where initializer - // lists are allowed to appear now, but might need to be made - // more strict if we allow for initializer lists in more - // places in the language (e.g., as function arguments). - // - if (outCost) - { - *outCost = kConversionCost_None; - } - - return true; + *outToExpr = createModifierCastExpr(toType, fromExpr); } + + return true; } + } - // nullptr_t can be cast into any pointer type. - if (as<NullPtrType>(fromType) && as<PtrType>(toType)) + // Coercion from an initializer list is allowed for many types, + // so we will farm that out to its own subroutine. + // + if (fromExpr && as<InitializerListType>(fromExpr->type.type)) + { + if (auto fromInitializerListExpr = as<InitializerListExpr>(fromExpr)) { - if (outCost) + if (!_coerceInitializerList(toType, outToExpr, fromInitializerListExpr)) { - *outCost = kConversionCost_NullPtrToPtr; + return false; } - if (outToExpr) + + // For now, we treat coercion from an initializer list + // as having no cost, so that all conversions from initializer + // lists are equally valid. This is fine given where initializer + // lists are allowed to appear now, but might need to be made + // more strict if we allow for initializer lists in more + // places in the language (e.g., as function arguments). + // + if (outCost) { - auto* defaultExpr = getASTBuilder()->create<DefaultConstructExpr>(); - defaultExpr->type = QualType(toType); - *outToExpr = defaultExpr; + *outCost = kConversionCost_None; } + return true; } - // none_t can be cast into any Optional<T> type. - if (as<NoneType>(fromType) && as<OptionalType>(toType)) + } + + // nullptr_t can be cast into any pointer type. + if (as<NullPtrType>(fromType) && as<PtrType>(toType)) + { + if (outCost) + { + *outCost = kConversionCost_NullPtrToPtr; + } + if (outToExpr) + { + auto* defaultExpr = getASTBuilder()->create<DefaultConstructExpr>(); + defaultExpr->type = QualType(toType); + *outToExpr = defaultExpr; + } + return true; + } + // none_t can be cast into any Optional<T> type. + if (as<NoneType>(fromType) && as<OptionalType>(toType)) + { + if (outCost) + { + *outCost = kConversionCost_NoneToOptional; + } + if (outToExpr) + { + auto resultExpr = getASTBuilder()->create<MakeOptionalExpr>(); + resultExpr->loc = fromExpr->loc; + resultExpr->type = toType; + *outToExpr = resultExpr; + } + return true; + } + + // A enum type can be converted into its underlying tag type. + if (auto enumDecl = isEnumType(fromType)) + { + Type* tagType = enumDecl->tagType; + if (tagType == toType) { if (outCost) { - *outCost = kConversionCost_NoneToOptional; + *outCost = kConversionCost_RankPromotion; } if (outToExpr) { - auto resultExpr = getASTBuilder()->create<MakeOptionalExpr>(); - resultExpr->loc = fromExpr->loc; - resultExpr->type = toType; - *outToExpr = resultExpr; + auto rsExpr = getASTBuilder()->create<BuiltinCastExpr>(); + rsExpr->type = toType; + rsExpr->loc = fromExpr->loc; + rsExpr->base = fromExpr; + *outToExpr = rsExpr; } return true; } + } - // A enum type can be converted into its underlying tag type. - if (auto enumDecl = isEnumType(fromType)) + // matrix type with different layouts are convertible + if (auto fromMatrixType = as<MatrixExpressionType>(fromType)) + { + if (auto toMatrixType = as<MatrixExpressionType>(toType)) { - Type* tagType = enumDecl->tagType; - if (tagType == toType) + if (fromMatrixType->getElementType()->equals(toMatrixType->getElementType()) && + fromMatrixType->getRowCount()->equals(toMatrixType->getRowCount()) && + fromMatrixType->getColumnCount()->equals(toMatrixType->getColumnCount())) { if (outCost) { - *outCost = kConversionCost_RankPromotion; + *outCost = kConversionCost_MatrixLayout; } if (outToExpr) { - auto rsExpr = getASTBuilder()->create<BuiltinCastExpr>(); - rsExpr->type = toType; - rsExpr->loc = fromExpr->loc; - rsExpr->base = fromExpr; - *outToExpr = rsExpr; + *outToExpr = fromExpr; } return true; } } + } - // matrix type with different layouts are convertible - if (auto fromMatrixType = as<MatrixExpressionType>(fromType)) + // A type is always convertible to any of its supertypes. + // + if (auto witness = tryGetSubtypeWitness(fromType, toType)) + { + if (outToExpr) { - if (auto toMatrixType = as<MatrixExpressionType>(toType)) + *outToExpr = createCastToSuperTypeExpr(toType, fromExpr, witness); + + // If the original expression was an l-value, then the result + // of the cast may be an l-value itself. We want to be able + // to invoke `[mutating]` methods on a value that is cast to + // an interface it conforms to, and we also expect to be able + // to pass a value of a derived `struct` type into methods that + // expect a value of its base type. + // + if (fromExpr && fromExpr->type.isLeftValue) { - if (fromMatrixType->getElementType()->equals(toMatrixType->getElementType()) && - fromMatrixType->getRowCount()->equals(toMatrixType->getRowCount()) && - fromMatrixType->getColumnCount()->equals(toMatrixType->getColumnCount())) - { - if (outCost) - { - *outCost = kConversionCost_MatrixLayout; - } - if (outToExpr) - { - *outToExpr = fromExpr; - } - return true; - } + // If the original type is a concrete type and toType is an interface type, + // we need to wrap the original expression into a MakeExistential, and the + // result of MakeExistential is not an l-value. + bool toTypeIsInterface = isInterfaceType(toType); + bool fromTypeIsInterface = isInterfaceType(fromType); + if (!toTypeIsInterface || toTypeIsInterface == fromTypeIsInterface) + (*outToExpr)->type.isLeftValue = true; } - } - - // A type is always convertible to any of its supertypes. + if (outCost) + *outCost = kConversionCost_CastToInterface; + return true; + } + else if (auto fromIsToWitness = tryGetSubtypeWitness(toType, fromType)) + { + // Is toType and fromType the same via some type equality witness? + // If so there is no need to do any conversion. // - if(auto witness = tryGetSubtypeWitness(fromType, toType)) + if (isTypeEqualityWitness(fromIsToWitness)) { if (outToExpr) { - *outToExpr = createCastToSuperTypeExpr(toType, fromExpr, witness); - - // If the original expression was an l-value, then the result - // of the cast may be an l-value itself. We want to be able - // to invoke `[mutating]` methods on a value that is cast to - // an interface it conforms to, and we also expect to be able - // to pass a value of a derived `struct` type into methods that - // expect a value of its base type. - // - if (fromExpr && fromExpr->type.isLeftValue) - { - // If the original type is a concrete type and toType is an interface type, - // we need to wrap the original expression into a MakeExistential, and the - // result of MakeExistential is not an l-value. - bool toTypeIsInterface = isInterfaceType(toType); - bool fromTypeIsInterface = isInterfaceType(fromType); - if (!toTypeIsInterface || toTypeIsInterface == fromTypeIsInterface) - (*outToExpr)->type.isLeftValue = true; - } + *outToExpr = createCastToSuperTypeExpr(toType, fromExpr, fromIsToWitness); } if (outCost) - *outCost = kConversionCost_CastToInterface; + *outCost = 0; return true; } - else if (auto fromIsToWitness = tryGetSubtypeWitness(toType, fromType)) + } + + // Disallow converting to a ParameterGroupType. + // + // TODO(tfoley): Under what circumstances would this check ever be needed? + // + if (as<ParameterGroupType>(toType)) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + + // We allow implicit conversion of a parameter group type like + // `ConstantBuffer<X>` or `ParameterBlock<X>` to its element + // type `X`. + // + if (auto fromParameterGroupType = as<ParameterGroupType>(fromType)) + { + auto fromElementType = fromParameterGroupType->getElementType(); + + // If we convert, e.g., `ConstantBuffer<A> to `A`, we will allow + // subsequent conversion of `A` to `B` if such a conversion + // is possible. + // + ConversionCost subCost = kConversionCost_None; + + DerefExpr* derefExpr = nullptr; + if (outToExpr) { - // Is toType and fromType the same via some type equality witness? - // If so there is no need to do any conversion. - // - if (isTypeEqualityWitness(fromIsToWitness)) - { - if (outToExpr) - { - *outToExpr = createCastToSuperTypeExpr(toType, fromExpr, fromIsToWitness); - } - if (outCost) - *outCost = 0; - return true; - } + derefExpr = m_astBuilder->create<DerefExpr>(); + derefExpr->base = fromExpr; + derefExpr->type = QualType(fromElementType); } - // Disallow converting to a ParameterGroupType. - // - // TODO(tfoley): Under what circumstances would this check ever be needed? - // - if (as<ParameterGroupType>(toType)) + if (!_coerce(site, toType, outToExpr, fromElementType, derefExpr, &subCost)) { - return _failedCoercion(toType, outToExpr, fromExpr); + return false; } - // We allow implicit conversion of a parameter group type like - // `ConstantBuffer<X>` or `ParameterBlock<X>` to its element - // type `X`. - // - if(auto fromParameterGroupType = as<ParameterGroupType>(fromType)) + if (outCost) + *outCost = subCost + kConversionCost_ImplicitDereference; + return true; + } + + if (auto refType = as<RefTypeBase>(toType)) + { + ConversionCost cost; + if (!canCoerce(refType->getValueType(), fromType, fromExpr, &cost)) + return false; + if (as<RefType>(toType) && !fromExpr->type.isLeftValue) + return false; + ConversionCost subCost = kConversionCost_GetRef; + + MakeRefExpr* refExpr = nullptr; + if (outToExpr) { - auto fromElementType = fromParameterGroupType->getElementType(); + refExpr = m_astBuilder->create<MakeRefExpr>(); + refExpr->base = fromExpr; + refExpr->type = QualType(refType); + refExpr->type.isLeftValue = false; + *outToExpr = refExpr; + } + if (outCost) + *outCost = subCost; + return true; + } - // If we convert, e.g., `ConstantBuffer<A> to `A`, we will allow - // subsequent conversion of `A` to `B` if such a conversion - // is possible. - // - ConversionCost subCost = kConversionCost_None; - DerefExpr* derefExpr = nullptr; - if(outToExpr) - { - derefExpr = m_astBuilder->create<DerefExpr>(); - derefExpr->base = fromExpr; - derefExpr->type = QualType(fromElementType); - } + // Allow implicit dereferencing a reference type. + if (auto fromRefType = as<RefTypeBase>(fromType)) + { + auto fromValueType = fromRefType->getValueType(); - if(!_coerce( - site, - toType, - outToExpr, - fromElementType, - derefExpr, - &subCost)) - { - return false; - } + // If we convert, e.g., `ConstantBuffer<A> to `A`, we will allow + // subsequent conversion of `A` to `B` if such a conversion + // is possible. + // + ConversionCost subCost = kConversionCost_None; - if(outCost) - *outCost = subCost + kConversionCost_ImplicitDereference; - return true; + Expr* openRefExpr = nullptr; + if (outToExpr) + { + openRefExpr = maybeOpenRef(fromExpr); } - if (auto refType = as<RefTypeBase>(toType)) + if (!_coerce(site, toType, outToExpr, fromValueType, openRefExpr, &subCost)) { - ConversionCost cost; - if (!canCoerce(refType->getValueType(), fromType, fromExpr, &cost)) - return false; - if (as<RefType>(toType) && !fromExpr->type.isLeftValue) - return false; - ConversionCost subCost = kConversionCost_GetRef; - - MakeRefExpr* refExpr = nullptr; - if (outToExpr) - { - refExpr = m_astBuilder->create<MakeRefExpr>(); - refExpr->base = fromExpr; - refExpr->type = QualType(refType); - refExpr->type.isLeftValue = false; - *outToExpr = refExpr; - } - if (outCost) - *outCost = subCost; - return true; + return false; } + if (outCost) + *outCost = subCost + kConversionCost_ImplicitDereference; + return true; + } - // Allow implicit dereferencing a reference type. - if (auto fromRefType = as<RefTypeBase>(fromType)) - { - auto fromValueType = fromRefType->getValueType(); - // If we convert, e.g., `ConstantBuffer<A> to `A`, we will allow - // subsequent conversion of `A` to `B` if such a conversion - // is possible. - // - ConversionCost subCost = kConversionCost_None; + // The main general-purpose approach for conversion is + // using suitable marked initializer ("constructor") + // declarations on the target type. + // + // This is treated as a form of overload resolution, + // since we are effectively forming an overloaded + // call to one of the initializers in the target type. + + OverloadResolveContext overloadContext; + overloadContext.disallowNestedConversions = (site != CoercionSite::ExplicitCoercion); + overloadContext.argCount = 1; + List<Expr*> args; + args.add(fromExpr); + overloadContext.argTypes = &fromType.type; + overloadContext.args = &args; + overloadContext.sourceScope = m_outerScope; + overloadContext.originalExpr = nullptr; + if (fromExpr) + { + overloadContext.loc = fromExpr->loc; + overloadContext.funcLoc = fromExpr->loc; + } - Expr* openRefExpr = nullptr; - if (outToExpr) - { - openRefExpr = maybeOpenRef(fromExpr); - } + overloadContext.baseExpr = nullptr; + overloadContext.mode = OverloadResolveContext::Mode::JustTrying; - if (!_coerce( - site, - toType, - outToExpr, - fromValueType, - openRefExpr, - &subCost)) - { - return false; - } + // Since the lookup and resolution of all possible implicit conversions + // can be very costly, we use a cache to store the checking results. + ImplicitCastMethodKey implicitCastKey = ImplicitCastMethodKey(fromType, toType, fromExpr); + ImplicitCastMethod* cachedMethod = getShared()->tryGetImplicitCastMethod(implicitCastKey); + if (cachedMethod) + { + if (cachedMethod->conversionFuncOverloadCandidate.status != + OverloadCandidate::Status::Applicable) + { + return _failedCoercion(toType, outToExpr, fromExpr); + } + overloadContext.bestCandidateStorage = cachedMethod->conversionFuncOverloadCandidate; + overloadContext.bestCandidate = &overloadContext.bestCandidateStorage; + if (!outToExpr) + { + // If we are not requesting to create an expression, we can return early. if (outCost) - *outCost = subCost + kConversionCost_ImplicitDereference; + *outCost = cachedMethod->cost; return true; } - - - // The main general-purpose approach for conversion is - // using suitable marked initializer ("constructor") - // declarations on the target type. - // - // This is treated as a form of overload resolution, - // since we are effectively forming an overloaded - // call to one of the initializers in the target type. - - OverloadResolveContext overloadContext; - overloadContext.disallowNestedConversions = (site != CoercionSite::ExplicitCoercion); - overloadContext.argCount = 1; - List<Expr*> args; - args.add(fromExpr); - overloadContext.argTypes = &fromType.type; - overloadContext.args = &args; - overloadContext.sourceScope = m_outerScope; - overloadContext.originalExpr = nullptr; - if(fromExpr) + else { - overloadContext.loc = fromExpr->loc; - overloadContext.funcLoc = fromExpr->loc; + if (cachedMethod->isAmbiguous) + { + overloadContext.bestCandidate = nullptr; + overloadContext.bestCandidates.add(cachedMethod->conversionFuncOverloadCandidate); + } } + } - overloadContext.baseExpr = nullptr; - overloadContext.mode = OverloadResolveContext::Mode::JustTrying; - - // Since the lookup and resolution of all possible implicit conversions - // can be very costly, we use a cache to store the checking results. - ImplicitCastMethodKey implicitCastKey = ImplicitCastMethodKey(fromType, toType, fromExpr); - ImplicitCastMethod* cachedMethod = getShared()->tryGetImplicitCastMethod(implicitCastKey); + if (!overloadContext.bestCandidate) + { + AddTypeOverloadCandidates(toType, overloadContext); + } - if (cachedMethod) + // After all of the overload candidates have been added + // to the context and processed, we need to see whether + // there was one best overload or not. + // + if (overloadContext.bestCandidates.getCount() != 0) + { + // In this case there were multiple equally-good candidates to call. + // + // We will start by checking if the candidates are + // even applicable, because if not, then we shouldn't + // consider the conversion as possible. + // + if (overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Applicable) { - if (cachedMethod->conversionFuncOverloadCandidate.status != OverloadCandidate::Status::Applicable) - { - return _failedCoercion(toType, outToExpr, fromExpr); - } - overloadContext.bestCandidateStorage = cachedMethod->conversionFuncOverloadCandidate; - overloadContext.bestCandidate = &overloadContext.bestCandidateStorage; - if (!outToExpr) - { - // If we are not requesting to create an expression, we can return early. - if (outCost) - *outCost = cachedMethod->cost; - return true; - } - else + if (!cachedMethod) { - if (cachedMethod->isAmbiguous) - { - overloadContext.bestCandidate = nullptr; - overloadContext.bestCandidates.add(cachedMethod->conversionFuncOverloadCandidate); - } + getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); } + return _failedCoercion(toType, outToExpr, fromExpr); } - if (!overloadContext.bestCandidate) - { - AddTypeOverloadCandidates(toType, overloadContext); - } - - // After all of the overload candidates have been added - // to the context and processed, we need to see whether - // there was one best overload or not. + // If all of the candidates in `bestCandidates` are applicable, + // then we have an ambiguity. // - if(overloadContext.bestCandidates.getCount() != 0) + // We will compute a nominal conversion cost as the minimum over + // all the conversions available. + // + ConversionCost bestCost = kConversionCost_Explicit; + ImplicitCastMethod method; + for (auto candidate : overloadContext.bestCandidates) { - // In this case there were multiple equally-good candidates to call. - // - // We will start by checking if the candidates are - // even applicable, because if not, then we shouldn't - // consider the conversion as possible. - // - if (overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Applicable) + ConversionCost candidateCost = getImplicitConversionCostWithKnownArg( + candidate.item.declRef.getDecl(), + toType, + fromExpr); + if (candidateCost < bestCost) { - if (!cachedMethod) - { - getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); - } - return _failedCoercion(toType, outToExpr, fromExpr); + method.conversionFuncOverloadCandidate = candidate; + bestCost = candidateCost; } + } - // If all of the candidates in `bestCandidates` are applicable, - // then we have an ambiguity. - // - // We will compute a nominal conversion cost as the minimum over - // all the conversions available. - // - ConversionCost bestCost = kConversionCost_Explicit; - ImplicitCastMethod method; - for(auto candidate : overloadContext.bestCandidates) - { - ConversionCost candidateCost = getImplicitConversionCostWithKnownArg( - candidate.item.declRef.getDecl(), toType, fromExpr); - if (candidateCost < bestCost) - { - method.conversionFuncOverloadCandidate = candidate; - bestCost = candidateCost; - } - } + // Conceptually, we want to treat the conversion as + // possible, but report it as ambiguous if we actually + // need to reify the result as an expression. + // + if (outToExpr) + { + getSink()->diagnose(fromExpr, Diagnostics::ambiguousConversion, fromType, toType); - // Conceptually, we want to treat the conversion as - // possible, but report it as ambiguous if we actually - // need to reify the result as an expression. - // - if(outToExpr) - { - getSink()->diagnose(fromExpr, Diagnostics::ambiguousConversion, fromType, toType); + *outToExpr = CreateErrorExpr(fromExpr); + } - *outToExpr = CreateErrorExpr(fromExpr); - } + if (!cachedMethod) + { + method.isAmbiguous = true; + method.cost = bestCost; + getShared()->cacheImplicitCastMethod(implicitCastKey, method); + } + if (outCost) + *outCost = bestCost; + return true; + } + else if (overloadContext.bestCandidate) + { + // If there is a single best candidate for conversion, + // then we want to use it. + // + // It is possible that there was a single best candidate, + // but it wasn't actually usable, so we will check for + // that case first. + // + if (overloadContext.bestCandidate->status != OverloadCandidate::Status::Applicable) + { if (!cachedMethod) { - method.isAmbiguous = true; - method.cost = bestCost; - getShared()->cacheImplicitCastMethod(implicitCastKey, method); + getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); } - - if(outCost) - *outCost = bestCost; - return true; + return _failedCoercion(toType, outToExpr, fromExpr); } - else if(overloadContext.bestCandidate) + + // Next, we need to look at the implicit conversion + // cost associated with the initializer we are invoking. + // + ConversionCost cost = getImplicitConversionCostWithKnownArg( + overloadContext.bestCandidate->item.declRef.getDecl(), + toType, + fromExpr); + + // If the cost is too high to be usable as an + // implicit conversion, then we will report the + // conversion as possible (so that an overload involving + // this conversion will be selected over one without), + // but then emit a diagnostic when actually reifying + // the result expression. + // + if (outToExpr && site != CoercionSite::ExplicitCoercion) { - // If there is a single best candidate for conversion, - // then we want to use it. - // - // It is possible that there was a single best candidate, - // but it wasn't actually usable, so we will check for - // that case first. - // - if (overloadContext.bestCandidate->status != OverloadCandidate::Status::Applicable) + if (cost >= kConversionCost_Explicit) { - if (!cachedMethod) - { - getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); - } - return _failedCoercion(toType, outToExpr, fromExpr); + getSink()->diagnose(fromExpr, Diagnostics::typeMismatch, toType, fromType); + getSink()->diagnoseWithoutSourceView( + fromExpr, + Diagnostics::noteExplicitConversionPossible, + fromType, + toType); } - - // Next, we need to look at the implicit conversion - // cost associated with the initializer we are invoking. - // - ConversionCost cost = getImplicitConversionCostWithKnownArg( - overloadContext.bestCandidate->item.declRef.getDecl(), toType, fromExpr); - - // If the cost is too high to be usable as an - // implicit conversion, then we will report the - // conversion as possible (so that an overload involving - // this conversion will be selected over one without), - // but then emit a diagnostic when actually reifying - // the result expression. - // - if (outToExpr && site != CoercionSite::ExplicitCoercion) + else if (cost >= kConversionCost_Default) { - if (cost >= kConversionCost_Explicit) + // For general types of implicit conversions, we issue a warning, unless `fromExpr` + // is a known constant and we know it won't cause a problem. + bool shouldEmitGeneralWarning = true; + if (isScalarIntegerType(toType)) { - getSink()->diagnose(fromExpr, Diagnostics::typeMismatch, toType, fromType); - getSink()->diagnoseWithoutSourceView( - fromExpr, Diagnostics::noteExplicitConversionPossible, fromType, toType); - } - else if (cost >= kConversionCost_Default) - { - // For general types of implicit conversions, we issue a warning, unless `fromExpr` is a known constant - // and we know it won't cause a problem. - bool shouldEmitGeneralWarning = true; - if (isScalarIntegerType(toType)) + if (auto intVal = tryFoldIntegerConstantExpression( + fromExpr, + ConstantFoldingKind::CompileTime, + nullptr)) { - if (auto intVal = tryFoldIntegerConstantExpression(fromExpr, ConstantFoldingKind::CompileTime, nullptr)) + if (auto val = as<ConstantIntVal>(intVal)) { - if (auto val = as<ConstantIntVal>(intVal)) + if (isIntValueInRangeOfType(val->getValue(), toType)) { - if (isIntValueInRangeOfType(val->getValue(), toType)) - { - // OK. - shouldEmitGeneralWarning = false; - } + // OK. + shouldEmitGeneralWarning = false; } } } - if (shouldEmitGeneralWarning) - { - getSink()->diagnose(fromExpr, Diagnostics::unrecommendedImplicitConversion, fromType, toType); - } } - - if (site == CoercionSite::Argument) + if (shouldEmitGeneralWarning) { - auto builtinConversionKind = getImplicitConversionBuiltinKind( - overloadContext.bestCandidate->item.declRef.getDecl()); - if (builtinConversionKind == kBuiltinConversion_FloatToDouble) - { - if (!as<FloatingPointLiteralExpr>(fromExpr)) - getSink()->diagnose(fromExpr, Diagnostics::implicitConversionToDouble); - } + getSink()->diagnose( + fromExpr, + Diagnostics::unrecommendedImplicitConversion, + fromType, + toType); } } - if (fromType.isLeftValue) - { - // If we are implicitly casting the type of an l-value, we need to impose additional cost. - cost += kConversionCost_LValueCast; - } - if(outCost) - *outCost = cost; - if(outToExpr) + if (site == CoercionSite::Argument) { - // The logic here is a bit ugly, to deal with the fact that - // `CompleteOverloadCandidate` will, left to its own devices, - // construct a vanilla `InvokeExpr` to represent the call - // to the initializer we found, while we *want* it to - // create some variety of `ImplicitCastExpr`. - // - // Now, it just so happens that `CompleteOverloadCandidate` - // will use the "original" expression if one is available, - // so we'll create one and initialize it here. - // We fill in the location and arguments, but not the - // base expression (the callee), since that will come - // from the selected overload candidate. - // - InvokeExpr* castExpr = (site == CoercionSite::ExplicitCoercion) - ? m_astBuilder->create<ExplicitCastExpr>() - : createImplicitCastExpr(); - castExpr->loc = fromExpr->loc; - castExpr->arguments.add(fromExpr); - // - // Next we need to set our cast expression as the "original" - // expression and then complete the overload process. - // - overloadContext.originalExpr = castExpr; - *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate); - // - // However, the above isn't *quite* enough, because - // the process of completing the overload candidate - // might overwrite the argument list that was passed - // in to overload resolution, and in this case that - // "argument list" was just a pointer to `fromExpr`. - // - // That means we need to clear the argument list and - // reload it from `args[0]` to make sure that we - // got the arguments *after* any transformations - // were applied. - // For right now this probably doesn't matter, - // because we don't allow nested implicit conversions, - // but I'd rather play it safe. - // - castExpr->arguments.clear(); - castExpr->arguments.add(args[0]); + auto builtinConversionKind = getImplicitConversionBuiltinKind( + overloadContext.bestCandidate->item.declRef.getDecl()); + if (builtinConversionKind == kBuiltinConversion_FloatToDouble) + { + if (!as<FloatingPointLiteralExpr>(fromExpr)) + getSink()->diagnose(fromExpr, Diagnostics::implicitConversionToDouble); + } } - if (!cachedMethod) - getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{ *overloadContext.bestCandidate, cost }); - return true; } - if (!cachedMethod) + if (fromType.isLeftValue) { - getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); + // If we are implicitly casting the type of an l-value, we need to impose additional + // cost. + cost += kConversionCost_LValueCast; } - return _failedCoercion(toType, outToExpr, fromExpr); - } - - bool SemanticsVisitor::canCoerce( - Type* toType, - QualType fromType, - Expr* fromExpr, - ConversionCost* outCost) - { - // As an optimization, we will maintain a cache of conversion results - // for basic types such as scalars and vectors. - // - - bool shouldAddToCache = false; - ConversionCost cost; - TypeCheckingCache* typeCheckingCache = getLinkage()->getTypeCheckingCache(); + if (outCost) + *outCost = cost; - BasicTypeKeyPair cacheKey; - cacheKey.type1 = makeBasicTypeKey(toType); - cacheKey.type2 = makeBasicTypeKey(fromType, fromExpr); - - if( cacheKey.isValid()) + if (outToExpr) { - if (typeCheckingCache->conversionCostCache.tryGetValue(cacheKey, cost)) - { - if (outCost) - *outCost = cost; - return cost != kConversionCost_Impossible; - } - else - shouldAddToCache = true; + // The logic here is a bit ugly, to deal with the fact that + // `CompleteOverloadCandidate` will, left to its own devices, + // construct a vanilla `InvokeExpr` to represent the call + // to the initializer we found, while we *want* it to + // create some variety of `ImplicitCastExpr`. + // + // Now, it just so happens that `CompleteOverloadCandidate` + // will use the "original" expression if one is available, + // so we'll create one and initialize it here. + // We fill in the location and arguments, but not the + // base expression (the callee), since that will come + // from the selected overload candidate. + // + InvokeExpr* castExpr = (site == CoercionSite::ExplicitCoercion) + ? m_astBuilder->create<ExplicitCastExpr>() + : createImplicitCastExpr(); + castExpr->loc = fromExpr->loc; + castExpr->arguments.add(fromExpr); + // + // Next we need to set our cast expression as the "original" + // expression and then complete the overload process. + // + overloadContext.originalExpr = castExpr; + *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate); + // + // However, the above isn't *quite* enough, because + // the process of completing the overload candidate + // might overwrite the argument list that was passed + // in to overload resolution, and in this case that + // "argument list" was just a pointer to `fromExpr`. + // + // That means we need to clear the argument list and + // reload it from `args[0]` to make sure that we + // got the arguments *after* any transformations + // were applied. + // For right now this probably doesn't matter, + // because we don't allow nested implicit conversions, + // but I'd rather play it safe. + // + castExpr->arguments.clear(); + castExpr->arguments.add(args[0]); } + if (!cachedMethod) + getShared()->cacheImplicitCastMethod( + implicitCastKey, + ImplicitCastMethod{*overloadContext.bestCandidate, cost}); + return true; + } + if (!cachedMethod) + { + getShared()->cacheImplicitCastMethod(implicitCastKey, ImplicitCastMethod{}); + } + return _failedCoercion(toType, outToExpr, fromExpr); +} - // If there was no suitable entry in the cache, - // then we fall back to the general-purpose - // conversion checking logic. - // - // Note that we are passing in `nullptr` as - // the output expression to be constructed, - // which suppresses emission of any diagnostics - // during the coercion process. - // - bool rs = _coerce( - CoercionSite::General, - toType, - nullptr, - fromType, - fromExpr, - &cost); +bool SemanticsVisitor::canCoerce( + Type* toType, + QualType fromType, + Expr* fromExpr, + ConversionCost* outCost) +{ + // As an optimization, we will maintain a cache of conversion results + // for basic types such as scalars and vectors. + // - if (outCost) - *outCost = cost; + bool shouldAddToCache = false; + ConversionCost cost; + TypeCheckingCache* typeCheckingCache = getLinkage()->getTypeCheckingCache(); + + BasicTypeKeyPair cacheKey; + cacheKey.type1 = makeBasicTypeKey(toType); + cacheKey.type2 = makeBasicTypeKey(fromType, fromExpr); - if (shouldAddToCache) + if (cacheKey.isValid()) + { + if (typeCheckingCache->conversionCostCache.tryGetValue(cacheKey, cost)) { - if (!rs) - cost = kConversionCost_Impossible; - typeCheckingCache->conversionCostCache[cacheKey] = cost; + if (outCost) + *outCost = cost; + return cost != kConversionCost_Impossible; } - - return rs; + else + shouldAddToCache = true; } - TypeCastExpr* SemanticsVisitor::createImplicitCastExpr() + // If there was no suitable entry in the cache, + // then we fall back to the general-purpose + // conversion checking logic. + // + // Note that we are passing in `nullptr` as + // the output expression to be constructed, + // which suppresses emission of any diagnostics + // during the coercion process. + // + bool rs = _coerce(CoercionSite::General, toType, nullptr, fromType, fromExpr, &cost); + + if (outCost) + *outCost = cost; + + if (shouldAddToCache) { - return m_astBuilder->create<ImplicitCastExpr>(); + if (!rs) + cost = kConversionCost_Impossible; + typeCheckingCache->conversionCostCache[cacheKey] = cost; } - Expr* SemanticsVisitor::CreateImplicitCastExpr( - Type* toType, - Expr* fromExpr) - { - TypeCastExpr* castExpr = createImplicitCastExpr(); + return rs; +} - auto typeType = m_astBuilder->getTypeType(toType); +TypeCastExpr* SemanticsVisitor::createImplicitCastExpr() +{ + return m_astBuilder->create<ImplicitCastExpr>(); +} - auto typeExpr = m_astBuilder->create<SharedTypeExpr>(); - typeExpr->type.type = typeType; - typeExpr->base.type = toType; +Expr* SemanticsVisitor::CreateImplicitCastExpr(Type* toType, Expr* fromExpr) +{ + TypeCastExpr* castExpr = createImplicitCastExpr(); - castExpr->loc = fromExpr->loc; - castExpr->functionExpr = typeExpr; - castExpr->type = QualType(toType); - castExpr->arguments.add(fromExpr); - return castExpr; - } + auto typeType = m_astBuilder->getTypeType(toType); - Expr* SemanticsVisitor::createCastToSuperTypeExpr( - Type* toType, - Expr* fromExpr, - Val* witness) - { - CastToSuperTypeExpr* expr = m_astBuilder->create<CastToSuperTypeExpr>(); - expr->loc = fromExpr->loc; - expr->type = QualType(toType); - expr->valueArg = fromExpr; - expr->witnessArg = witness; - return expr; - } + auto typeExpr = m_astBuilder->create<SharedTypeExpr>(); + typeExpr->type.type = typeType; + typeExpr->base.type = toType; - Expr* SemanticsVisitor::createModifierCastExpr( - Type* toType, - Expr* fromExpr) - { - ModifierCastExpr* expr = m_astBuilder->create<ModifierCastExpr>(); - expr->loc = fromExpr->loc; - expr->type = QualType(toType); - expr->valueArg = fromExpr; - return expr; - } + castExpr->loc = fromExpr->loc; + castExpr->functionExpr = typeExpr; + castExpr->type = QualType(toType); + castExpr->arguments.add(fromExpr); + return castExpr; +} +Expr* SemanticsVisitor::createCastToSuperTypeExpr(Type* toType, Expr* fromExpr, Val* witness) +{ + CastToSuperTypeExpr* expr = m_astBuilder->create<CastToSuperTypeExpr>(); + expr->loc = fromExpr->loc; + expr->type = QualType(toType); + expr->valueArg = fromExpr; + expr->witnessArg = witness; + return expr; +} - Expr* SemanticsVisitor::coerce( - CoercionSite site, - Type* toType, - Expr* fromExpr) - { - Expr* expr = nullptr; - if (!_coerce( - site, - toType, - &expr, - fromExpr->type, - fromExpr, - nullptr)) - { - // Note(tfoley): We don't call `CreateErrorExpr` here, because that would - // clobber the type on `fromExpr`, and an invariant here is that coercion - // really shouldn't *change* the expression that is passed in, but should - // introduce new AST nodes to coerce its value to a different type... - return CreateImplicitCastExpr( - m_astBuilder->getErrorType(), - fromExpr); - } +Expr* SemanticsVisitor::createModifierCastExpr(Type* toType, Expr* fromExpr) +{ + ModifierCastExpr* expr = m_astBuilder->create<ModifierCastExpr>(); + expr->loc = fromExpr->loc; + expr->type = QualType(toType); + expr->valueArg = fromExpr; + return expr; +} - return expr; - } - bool SemanticsVisitor::canConvertImplicitly( - ConversionCost conversionCost) +Expr* SemanticsVisitor::coerce(CoercionSite site, Type* toType, Expr* fromExpr) +{ + Expr* expr = nullptr; + if (!_coerce(site, toType, &expr, fromExpr->type, fromExpr, nullptr)) { - // Is the conversion cheap enough to be done implicitly? - if (conversionCost >= kConversionCost_GeneralConversion) - return false; - return true; + // Note(tfoley): We don't call `CreateErrorExpr` here, because that would + // clobber the type on `fromExpr`, and an invariant here is that coercion + // really shouldn't *change* the expression that is passed in, but should + // introduce new AST nodes to coerce its value to a different type... + return CreateImplicitCastExpr(m_astBuilder->getErrorType(), fromExpr); } - bool SemanticsVisitor::canConvertImplicitly( - Type* toType, - QualType fromType) - { - auto conversionCost = getConversionCost(toType, fromType); + return expr; +} - // Is the conversion cheap enough to be done implicitly? - if (canConvertImplicitly(conversionCost)) - return false; +bool SemanticsVisitor::canConvertImplicitly(ConversionCost conversionCost) +{ + // Is the conversion cheap enough to be done implicitly? + if (conversionCost >= kConversionCost_GeneralConversion) + return false; + return true; +} - return true; - } +bool SemanticsVisitor::canConvertImplicitly(Type* toType, QualType fromType) +{ + auto conversionCost = getConversionCost(toType, fromType); - ConversionCost SemanticsVisitor::getConversionCost(Type* toType, QualType fromType) - { - ConversionCost conversionCost = kConversionCost_Impossible; - if (!canCoerce(toType, fromType, nullptr, &conversionCost)) - return kConversionCost_Impossible; - return conversionCost; - } + // Is the conversion cheap enough to be done implicitly? + if (canConvertImplicitly(conversionCost)) + return false; + + return true; +} + +ConversionCost SemanticsVisitor::getConversionCost(Type* toType, QualType fromType) +{ + ConversionCost conversionCost = kConversionCost_Impossible; + if (!canCoerce(toType, fromType, nullptr, &conversionCost)) + return kConversionCost_Impossible; + return conversionCost; } +} // namespace Slang |
