|
| 1 | +package org.enso.compiler.pass.resolve; |
| 2 | + |
| 3 | +import java.util.ArrayList; |
| 4 | +import org.enso.compiler.MetadataInteropHelpers; |
| 5 | +import org.enso.compiler.context.InlineContext; |
| 6 | +import org.enso.compiler.context.ModuleContext; |
| 7 | +import org.enso.compiler.core.CompilerError; |
| 8 | +import org.enso.compiler.core.IR; |
| 9 | +import org.enso.compiler.core.ir.DefinitionArgument; |
| 10 | +import org.enso.compiler.core.ir.Expression; |
| 11 | +import org.enso.compiler.core.ir.Function; |
| 12 | +import org.enso.compiler.core.ir.MetadataStorage; |
| 13 | +import org.enso.compiler.core.ir.Module; |
| 14 | +import org.enso.compiler.core.ir.Name; |
| 15 | +import org.enso.compiler.core.ir.expression.errors.Conversion; |
| 16 | +import org.enso.compiler.core.ir.expression.errors.Conversion.UnsupportedSourceType$; |
| 17 | +import org.enso.compiler.core.ir.module.scope.Definition; |
| 18 | +import org.enso.compiler.core.ir.module.scope.definition.Method; |
| 19 | +import org.enso.compiler.data.BindingsMap; |
| 20 | +import org.enso.compiler.data.BindingsMap.Resolution; |
| 21 | +import org.enso.compiler.data.BindingsMap.ResolvedConstructor; |
| 22 | +import org.enso.compiler.data.BindingsMap.ResolvedConversionMethod; |
| 23 | +import org.enso.compiler.data.BindingsMap.ResolvedExtensionMethod; |
| 24 | +import org.enso.compiler.data.BindingsMap.ResolvedModule; |
| 25 | +import org.enso.compiler.data.BindingsMap.ResolvedModuleMethod; |
| 26 | +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; |
| 27 | +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; |
| 28 | +import org.enso.compiler.data.BindingsMap.ResolvedType; |
| 29 | +import org.enso.compiler.data.BindingsMap.Type; |
| 30 | +import org.enso.compiler.pass.IRPass; |
| 31 | +import org.enso.compiler.pass.IRProcessingPass; |
| 32 | +import org.enso.compiler.pass.analyse.BindingAnalysis$; |
| 33 | +import org.enso.compiler.pass.desugar.ComplexType$; |
| 34 | +import org.enso.compiler.pass.desugar.FunctionBinding$; |
| 35 | +import org.enso.compiler.pass.desugar.GenerateMethodBodies$; |
| 36 | +import scala.Option; |
| 37 | +import scala.collection.immutable.List; |
| 38 | +import scala.collection.immutable.Seq; |
| 39 | +import scala.jdk.javaapi.CollectionConverters; |
| 40 | + |
| 41 | +/** |
| 42 | + * Resolves the correct {@code self} argument type for method definitions and stores the resolution |
| 43 | + * in the method's metadata. |
| 44 | + * |
| 45 | + * <p>Metadata type is {@link BindingsMap.Resolution} |
| 46 | + */ |
| 47 | +public class MethodDefinitions implements IRPass { |
| 48 | + public static final MethodDefinitions INSTANCE = new MethodDefinitions(); |
| 49 | + |
| 50 | + @Override |
| 51 | + public Seq<IRProcessingPass> precursorPasses() { |
| 52 | + java.util.List<IRProcessingPass> passes = |
| 53 | + java.util.List.of( |
| 54 | + ComplexType$.MODULE$, |
| 55 | + FunctionBinding$.MODULE$, |
| 56 | + GenerateMethodBodies$.MODULE$, |
| 57 | + BindingAnalysis$.MODULE$); |
| 58 | + return CollectionConverters.asScala(passes).toList(); |
| 59 | + } |
| 60 | + |
| 61 | + @Override |
| 62 | + @SuppressWarnings("unchecked") |
| 63 | + public Seq<IRProcessingPass> invalidatedPasses() { |
| 64 | + Object obj = scala.collection.immutable.Nil$.MODULE$; |
| 65 | + return (scala.collection.immutable.List<IRProcessingPass>) obj; |
| 66 | + } |
| 67 | + |
| 68 | + @Override |
| 69 | + public Module runModule(Module ir, ModuleContext moduleContext) { |
| 70 | + BindingsMap availableSymbolsMap = |
| 71 | + MetadataInteropHelpers.getMetadata(ir, BindingAnalysis$.MODULE$, BindingsMap.class); |
| 72 | + var newDefs = |
| 73 | + ir.bindings() |
| 74 | + .map( |
| 75 | + def -> { |
| 76 | + if (def instanceof Method method) { |
| 77 | + var methodRef = method.methodReference(); |
| 78 | + Option<Name> resolvedTypeRef = |
| 79 | + methodRef |
| 80 | + .typePointer() |
| 81 | + .map(tp -> resolveType(tp, availableSymbolsMap)) |
| 82 | + .orElse(null); |
| 83 | + var resolvedMethodRef = methodRef.copyWithTypePointer(resolvedTypeRef); |
| 84 | + |
| 85 | + return switch (method) { |
| 86 | + case Method.Explicit explicitMethod -> { |
| 87 | + var isStatic = computeIsStatic(explicitMethod.body()); |
| 88 | + var resolvedMethod = |
| 89 | + explicitMethod.copy( |
| 90 | + resolvedMethodRef, |
| 91 | + explicitMethod.body(), |
| 92 | + isStatic, |
| 93 | + explicitMethod.isPrivate(), |
| 94 | + explicitMethod.isStaticWrapperForInstanceMethod(), |
| 95 | + explicitMethod.location(), |
| 96 | + explicitMethod.passData(), |
| 97 | + explicitMethod.diagnostics(), |
| 98 | + explicitMethod.id()); |
| 99 | + yield resolvedMethod; |
| 100 | + } |
| 101 | + case Method.Conversion conversionMethod -> { |
| 102 | + var sourceTypeExpr = conversionMethod.sourceTypeName(); |
| 103 | + Name resolvedName = |
| 104 | + switch (sourceTypeExpr) { |
| 105 | + case Name name -> resolveType(name, availableSymbolsMap); |
| 106 | + default -> new Conversion( |
| 107 | + sourceTypeExpr, |
| 108 | + UnsupportedSourceType$.MODULE$, |
| 109 | + MetadataStorage.EMPTY); |
| 110 | + }; |
| 111 | + var resolvedMethod = |
| 112 | + conversionMethod.copy( |
| 113 | + resolvedMethodRef, |
| 114 | + resolvedName, |
| 115 | + conversionMethod.body(), |
| 116 | + conversionMethod.location(), |
| 117 | + conversionMethod.passData(), |
| 118 | + conversionMethod.diagnostics(), |
| 119 | + conversionMethod.id()); |
| 120 | + yield resolvedMethod; |
| 121 | + } |
| 122 | + default -> throw new CompilerError( |
| 123 | + "Unexpected method type in MethodDefinitions pass."); |
| 124 | + }; |
| 125 | + } else { |
| 126 | + return def; |
| 127 | + } |
| 128 | + }); |
| 129 | + |
| 130 | + java.util.List<Definition> withStaticAliases = new ArrayList<>(); |
| 131 | + for (var def : CollectionConverters.asJava(newDefs)) { |
| 132 | + withStaticAliases.add(def); |
| 133 | + if (def instanceof Method.Explicit method && !method.isStatic()) { |
| 134 | + var staticAlias = generateStaticAliasMethod(method); |
| 135 | + if (staticAlias != null) { |
| 136 | + withStaticAliases.add(staticAlias); |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + return ir.copyWithBindings(CollectionConverters.asScala(withStaticAliases).toList()); |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * Returns null if there is no suitable static alias method that can be generated for the given |
| 146 | + * {@code method}. |
| 147 | + * |
| 148 | + * @param method Non-static method from which a static alias method is generated. |
| 149 | + * @return Static alias method for the given {@code method} or null. |
| 150 | + */ |
| 151 | + private Method.Explicit generateStaticAliasMethod(Method.Explicit method) { |
| 152 | + assert !method.isStatic(); |
| 153 | + var typePointer = method.methodReference().typePointer(); |
| 154 | + if (typePointer.isEmpty()) { |
| 155 | + return null; |
| 156 | + } |
| 157 | + var resolution = |
| 158 | + MetadataInteropHelpers.getMetadataOrNull(typePointer.get(), this, Resolution.class); |
| 159 | + if (resolution == null) { |
| 160 | + return null; |
| 161 | + } |
| 162 | + if (resolution.target() instanceof ResolvedType resType |
| 163 | + && canGenerateStaticWrappers(resType.tp())) { |
| 164 | + assert method.body() instanceof Function.Lambda; |
| 165 | + var dup = method.duplicate(true, true, true, false); |
| 166 | + // This is the self argument that will receive the `SelfType.type` value upon dispatch, it is |
| 167 | + // added to avoid modifying the dispatch mechanism. |
| 168 | + var syntheticModuleSelfArg = |
| 169 | + new DefinitionArgument.Specified( |
| 170 | + new Name.Self(null, true, MetadataStorage.EMPTY), |
| 171 | + Option.empty(), |
| 172 | + Option.empty(), |
| 173 | + false, |
| 174 | + null, |
| 175 | + MetadataStorage.EMPTY); |
| 176 | + var newBody = |
| 177 | + new Function.Lambda( |
| 178 | + // This is the synthetic Self argument that gets the static module |
| 179 | + list(syntheticModuleSelfArg), |
| 180 | + // Here we add the type ascription ensuring that the 'proper' self argument only |
| 181 | + // accepts _instances_ of the type (or triggers conversions) |
| 182 | + addTypeAscriptionToSelfArgument(dup.body()), |
| 183 | + null, |
| 184 | + true, |
| 185 | + MetadataStorage.EMPTY); |
| 186 | + // The actual `self` argument that is referenced inside of method body is the second one in |
| 187 | + // the lambda. |
| 188 | + // This is the argument that will hold the actual instance of the object we are calling on, |
| 189 | + // e.g. `My_Type.method instance`. |
| 190 | + // We add a type check to it to ensure only `instance` of `My_Type` can be passed to it. |
| 191 | + var staticMethod = |
| 192 | + dup.copy( |
| 193 | + method.methodReference(), |
| 194 | + newBody, |
| 195 | + true, |
| 196 | + method.isPrivate(), |
| 197 | + true, |
| 198 | + method.location(), |
| 199 | + method.passData(), |
| 200 | + method.diagnostics(), |
| 201 | + method.id()); |
| 202 | + return staticMethod; |
| 203 | + } |
| 204 | + return null; |
| 205 | + } |
| 206 | + |
| 207 | + private static Expression addTypeAscriptionToSelfArgument(Expression methodBody) { |
| 208 | + if (methodBody instanceof Function.Lambda lambda) { |
| 209 | + if (lambda.arguments().isEmpty()) { |
| 210 | + throw new CompilerError( |
| 211 | + "MethodDefinitions pass: expected at least one argument (self) in the method, but got" |
| 212 | + + " none."); |
| 213 | + } |
| 214 | + var firstArg = lambda.arguments().head(); |
| 215 | + if (firstArg instanceof DefinitionArgument.Specified selfArg |
| 216 | + && selfArg.name() instanceof Name.Self) { |
| 217 | + var selfType = new Name.SelfType(selfArg.identifiedLocation(), MetadataStorage.EMPTY); |
| 218 | + var newSelfArg = selfArg.copyWithAscribedType(selfType); |
| 219 | + return lambdaWithNewSelfArg(lambda, newSelfArg); |
| 220 | + } else { |
| 221 | + throw new CompilerError( |
| 222 | + "MethodDefinitions pass: expected the first argument to be `self`, but got " |
| 223 | + + firstArg); |
| 224 | + } |
| 225 | + } else { |
| 226 | + throw new CompilerError("Unexpected body type " + methodBody + " in MethodDefinitions pass."); |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + private static Function.Lambda lambdaWithNewSelfArg( |
| 231 | + Function.Lambda lambda, DefinitionArgument newSelfArg) { |
| 232 | + var args = CollectionConverters.asJava(lambda.arguments()); |
| 233 | + assert !args.isEmpty(); |
| 234 | + args.set(0, newSelfArg); |
| 235 | + var newArgs = CollectionConverters.asScala(args).toList(); |
| 236 | + return lambda.copyWithArguments(newArgs); |
| 237 | + } |
| 238 | + |
| 239 | + // Generate static wrappers for |
| 240 | + // 1. Types having at least one type constructor |
| 241 | + // 2. All builtin types except for Nothing. Nothing's eigentype is Nothing and not Nothing.type, |
| 242 | + // would lead to overriding conflicts. |
| 243 | + // TODO: Remove the hardcoded type once Enso's annotations can define parameters. |
| 244 | + private static boolean canGenerateStaticWrappers(Type tp) { |
| 245 | + return tp.members().nonEmpty() || (tp.builtinType() && !"Nothing".equals(tp.name())); |
| 246 | + } |
| 247 | + |
| 248 | + private Name resolveType(Name typePointer, BindingsMap availableSymbolsMap) { |
| 249 | + if (typePointer instanceof Name.Qualified || typePointer instanceof Name.Literal) { |
| 250 | + var items = |
| 251 | + switch (typePointer) { |
| 252 | + case Name.Qualified qualName -> qualName.parts().map(Name::name); |
| 253 | + case Name.Literal lit -> list(lit.name()); |
| 254 | + default -> throw new CompilerError("Impossible to reach"); |
| 255 | + }; |
| 256 | + |
| 257 | + var resolvedItemsOpt = availableSymbolsMap.resolveQualifiedName(items); |
| 258 | + if (resolvedItemsOpt.isLeft()) { |
| 259 | + var err = resolvedItemsOpt.swap().toOption().get(); |
| 260 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 261 | + typePointer, |
| 262 | + new org.enso.compiler.core.ir.expression.errors.Resolution.ResolverError(err), |
| 263 | + MetadataStorage.EMPTY); |
| 264 | + } |
| 265 | + var resolvedItems = resolvedItemsOpt.toOption().get(); |
| 266 | + assert resolvedItems.size() == 1 : "Expected a single resolution"; |
| 267 | + switch (resolvedItems.head()) { |
| 268 | + case ResolvedConstructor ignored -> { |
| 269 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 270 | + typePointer, |
| 271 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedConstructor( |
| 272 | + "a method definition target"), |
| 273 | + MetadataStorage.EMPTY); |
| 274 | + } |
| 275 | + case ResolvedModule resMod -> { |
| 276 | + MetadataInteropHelpers.updateMetadata(typePointer, this, new Resolution(resMod)); |
| 277 | + return typePointer; |
| 278 | + } |
| 279 | + case ResolvedType resType -> { |
| 280 | + MetadataInteropHelpers.updateMetadata(typePointer, this, new Resolution(resType)); |
| 281 | + return typePointer; |
| 282 | + } |
| 283 | + case ResolvedPolyglotSymbol ignored -> { |
| 284 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 285 | + typePointer, |
| 286 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedPolyglot( |
| 287 | + "a method definition target"), |
| 288 | + MetadataStorage.EMPTY); |
| 289 | + } |
| 290 | + case ResolvedPolyglotField ignored -> { |
| 291 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 292 | + typePointer, |
| 293 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedPolyglot( |
| 294 | + "a method definition target"), |
| 295 | + MetadataStorage.EMPTY); |
| 296 | + } |
| 297 | + case ResolvedModuleMethod ignored -> { |
| 298 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 299 | + typePointer, |
| 300 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( |
| 301 | + "a method definition target"), |
| 302 | + MetadataStorage.EMPTY); |
| 303 | + } |
| 304 | + case ResolvedExtensionMethod ignored -> { |
| 305 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 306 | + typePointer, |
| 307 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( |
| 308 | + "a static method definition target"), |
| 309 | + MetadataStorage.EMPTY); |
| 310 | + } |
| 311 | + case ResolvedConversionMethod ignored -> { |
| 312 | + return new org.enso.compiler.core.ir.expression.errors.Resolution( |
| 313 | + typePointer, |
| 314 | + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( |
| 315 | + "a conversion method definition target"), |
| 316 | + MetadataStorage.EMPTY); |
| 317 | + } |
| 318 | + default -> throw new IllegalStateException("Unexpected value: " + resolvedItems.head()); |
| 319 | + } |
| 320 | + } else if (typePointer instanceof org.enso.compiler.core.ir.expression.errors.Resolution) { |
| 321 | + return typePointer; |
| 322 | + } else { |
| 323 | + throw new CompilerError("Unexpected kind of name for method reference"); |
| 324 | + } |
| 325 | + } |
| 326 | + |
| 327 | + private static <T> List<T> list(T item) { |
| 328 | + return CollectionConverters.asScala(java.util.List.of(item)).toList(); |
| 329 | + } |
| 330 | + |
| 331 | + @Override |
| 332 | + public Expression runExpression(Expression ir, InlineContext inlineContext) { |
| 333 | + return ir; |
| 334 | + } |
| 335 | + |
| 336 | + private static boolean computeIsStatic(IR body) { |
| 337 | + return Method.Explicit$.MODULE$.computeIsStatic(body); |
| 338 | + } |
| 339 | +} |
0 commit comments