Просмотр исходного кода

Merge branch 'development' into coroutines_2024

Simon Krajewski 1 год назад
Родитель
Сommit
4126322f43
42 измененных файлов с 917 добавлено и 641 удалено
  1. 4 241
      .github/workflows/main.yml
  2. 4 61
      extra/github-actions/workflows/main.yml
  3. 6 0
      src-json/meta.json
  4. 10 0
      src/codegen/javaModern.ml
  5. 12 6
      src/compiler/hxb/hxbData.ml
  6. 25 7
      src/compiler/hxb/hxbReader.ml
  7. 88 21
      src/compiler/hxb/hxbWriter.ml
  8. 4 2
      src/compiler/server.ml
  9. 11 2
      src/context/abstractCast.ml
  10. 3 0
      src/context/common.ml
  11. 1 1
      src/context/display/displayTexpr.ml
  12. 0 2
      src/context/typecore.ml
  13. 20 0
      src/core/tOther.ml
  14. 11 0
      src/core/tUnification.ml
  15. 65 14
      src/generators/genjvm.ml
  16. 18 91
      src/generators/jvm/jvmFunctions.ml
  17. 2 1
      src/macro/eval/evalStdLib.ml
  18. 1 0
      src/macro/eval/evalValue.ml
  19. 3 19
      src/typing/typeloadFields.ml
  20. 7 1
      src/typing/typeloadModule.ml
  21. 0 1
      src/typing/typerEntry.ml
  22. 10 8
      std/String.hx
  23. 9 17
      std/UnicodeString.hx
  24. 5 2
      std/jvm/StringExt.hx
  25. 14 9
      std/jvm/_std/Reflect.hx
  26. 19 12
      std/lua/_std/String.hx
  27. 15 6
      std/neko/_std/String.hx
  28. 24 28
      std/php/Boot.hx
  29. 12 2
      std/python/_std/sys/net/Socket.hx
  30. 22 11
      std/python/internal/StringImpl.hx
  31. 29 0
      std/python/lib/ssl/Errors.hx
  32. 1 0
      tests/misc/java/projects/Issue11014/Main.hx
  33. 4 4
      tests/misc/java/projects/Issue11014/compile.hxml.stdout
  34. 1 0
      tests/misc/java/projects/Issue11054/pack/ItemGroupEvents.java
  35. 16 6
      tests/runci/targets/Lua.hx
  36. 2 1
      tests/unit/src/unit/TestResource.hx
  37. 3 1
      tests/unit/src/unit/issues/Issue11054.hx
  38. 30 0
      tests/unit/src/unit/issues/Issue11236.hx
  39. 70 0
      tests/unit/src/unit/issues/Issue5271.hx
  40. 268 0
      tests/unit/src/unit/issues/Issue6705.hx
  41. 46 37
      tests/unit/src/unitstd/String.unit.hx
  42. 22 27
      tests/unit/src/unitstd/UnicodeString.unit.hx

+ 4 - 241
.github/workflows/main.yml

@@ -120,140 +120,6 @@ jobs:
           path: out
 
 
-  windows-build:
-    runs-on: windows-latest
-    env:
-      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
-      PLATFORM: windows
-      ARCH: 32
-      MINGW_ARCH: i686
-      CYG_ROOT: D:\cygwin
-    steps:
-      - uses: actions/checkout@main
-        with:
-          submodules: recursive
-
-      - name: Use GNU Tar from msys
-        run: |
-          echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
-          rm C:\msys64\usr\bin\bash.exe
-
-      - name: choco install nsis
-        uses: nick-invision/retry@v2
-        with:
-          timeout_minutes: 10
-          max_attempts: 10
-          command: choco install --no-progress nsis.portable --version 3.09 -y
-
-      - name: choco install things
-        shell: pwsh
-        run: choco install --no-progress curl wget 7zip.portable -y
-
-      - name: Prepend Chocolatey path
-        shell: pwsh
-        run: Write-Host "::add-path::C:\ProgramData\chocolatey\bin"
-
-      - name: Install Neko from S3
-        shell: pwsh
-        run: |
-          Invoke-WebRequest https://build.haxe.org/builds/neko/$env:PLATFORM/neko_latest.zip -OutFile $env:RUNNER_TEMP/neko_latest.zip
-          Expand-Archive $env:RUNNER_TEMP/neko_latest.zip -DestinationPath $env:RUNNER_TEMP
-          $NEKOPATH = Get-ChildItem $env:RUNNER_TEMP/neko-*-*
-          echo "$NEKOPATH" >> $env:GITHUB_PATH
-          echo "NEKOPATH=$NEKOPATH" >> $env:GITHUB_ENV
-
-      - name: Print Neko version
-        run: neko -version 2>&1
-
-      - name: Setup ocaml
-        id: ocaml
-        continue-on-error: true
-        uses: kLabz/setup-ocaml@win32
-        with:
-          ocaml-compiler: 4.08.1
-          opam-depext: false
-          opam-repositories: |
-            opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset
-            default: https://github.com/ocaml/opam-repository.git
-          opam-local-packages: |
-            haxe.opam
-          cache-prefix: w32-v1
-
-      # TODO make it work on first try
-      # (when cygwin cache doesn't exist, ocaml install fails with a curl error)
-      - name: Setup ocaml (second chance)
-        if: steps.ocaml.outcome == 'failure'
-        uses: kLabz/setup-ocaml@win32
-        with:
-          ocaml-compiler: 4.08.1
-          opam-depext: false
-          opam-repositories: |
-            opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset
-            default: https://github.com/ocaml/opam-repository.git
-          opam-local-packages: |
-            haxe.opam
-          cache-prefix: w32-v1
-
-      - name: Install dependencies
-        shell: pwsh
-        run: |
-          Set-PSDebug -Trace 1
-          curl.exe -fsSL -o "libmbedtls.tar.xz" --retry 3 https://github.com/Simn/mingw64-mbedtls/releases/download/2.16.3/mingw64-$($env:MINGW_ARCH)-mbedtls-2.16.3-1.tar.xz
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'curl -L https://cpanmin.us | perl - App::cpanminus')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cpanm IPC::System::Simple module')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cpanm String::ShellQuote')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'echo "$OLDPWD"')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && tar -C / -xvf libmbedtls.tar.xz')
-
-      - name: Install OCaml libraries
-        shell: pwsh
-        run: |
-          Set-PSDebug -Trace 1
-          opam install haxe --deps-only
-          opam list
-
-      - name: Expose mingw dll files
-        shell: pwsh
-        run: Write-Host "::add-path::${env:CYG_ROOT}/usr/$($env:MINGW_ARCH)-w64-mingw32/sys-root/mingw/bin"
-
-      # required to be able to retrieve the revision
-      - name: Mark directory as safe
-        shell: pwsh
-        run: |
-          Set-PSDebug -Trace 1
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'git config --global --add safe.directory "$OLDPWD"')
-
-      - name: Set ADD_REVISION=1 for non-release
-        if: ${{ !startsWith(github.ref, 'refs/tags/') }}
-        shell: pwsh
-        run: echo "ADD_REVISION=1" >> $Env:GITHUB_ENV
-
-      - name: Build Haxe
-        shell: pwsh
-        run: |
-          Set-PSDebug -Trace 1
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && opam config exec -- make -s -f Makefile.win -j`nproc` haxe 2>&1')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && opam config exec -- make -s -f Makefile.win haxelib 2>&1')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && opam config exec -- make -f Makefile.win echo_package_files package_bin package_installer_win package_choco 2>&1')
-          dir out
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && cygcheck ./haxe.exe')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && cygcheck ./haxelib.exe')
-          & "$($env:CYG_ROOT)/bin/bash.exe" @('-lc', 'cd "$OLDPWD" && ls ./out')
-
-      - name: Check artifact
-        shell: bash
-        run: |
-          ls out
-          # Output should contain binaries zip, installer zip and nupkg
-          [ $(ls -1 out | wc -l) -eq "3" ]
-
-      - name: Upload artifact
-        uses: actions/upload-artifact@v3
-        with:
-          name: win${{env.ARCH}}Binaries
-          path: out
-
-
   linux-build:
     runs-on: ubuntu-20.04
     env:
@@ -773,103 +639,6 @@ jobs:
         working-directory: ${{github.workspace}}/tests
 
 
-  windows-test:
-    needs: windows-build
-    runs-on: windows-latest
-    env:
-      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
-      PLATFORM: windows
-      TEST: ${{matrix.target}}
-      HXCPP_COMPILE_CACHE: ~/hxcache
-      ARCH: 32
-    strategy:
-      fail-fast: false
-      matrix:
-        # TODO jvm: https://github.com/HaxeFoundation/haxe/issues/8601
-        # TODO enable lua after https://github.com/HaxeFoundation/haxe/issues/10919
-        target: [macro, js, hl, cpp, php, python, flash, neko]
-    steps:
-      - uses: actions/checkout@main
-        with:
-          submodules: recursive
-      - uses: actions/download-artifact@v3
-        with:
-          name: win${{env.ARCH}}Binaries
-          path: win${{env.ARCH}}Binaries
-
-      - name: Install Neko from S3
-        shell: pwsh
-        run: |
-          Invoke-WebRequest https://build.haxe.org/builds/neko/$env:PLATFORM/neko_latest.zip -OutFile $env:RUNNER_TEMP/neko_latest.zip
-          Expand-Archive $env:RUNNER_TEMP/neko_latest.zip -DestinationPath $env:RUNNER_TEMP
-          $NEKOPATH = Get-ChildItem $env:RUNNER_TEMP/neko-*-*
-          echo "$NEKOPATH" >> $env:GITHUB_PATH
-          echo "NEKOPATH=$NEKOPATH" >> $env:GITHUB_ENV
-
-      - name: Print Neko version
-        run: neko -version 2>&1
-
-      - uses: actions/setup-node@v3
-        with:
-          node-version: 18.17.1
-
-      # - name: Quick test
-      #   shell: pwsh
-      #   run: |
-      #     $DOWNLOADDIR="./win$($env:ARCH)Binaries"
-      #     new-item -Name $DOWNLOADDIR -ItemType directory
-      #     Invoke-WebRequest https://build.haxe.org/builds/haxe/$env:PLATFORM/haxe_latest.zip -OutFile $DOWNLOADDIR/haxe_bin.zip
-
-      - name: Setup Haxe
-        shell: pwsh
-        run: |
-          $DOWNLOADDIR="./win$($env:ARCH)Binaries"
-          Expand-Archive $DOWNLOADDIR/*_bin.zip -DestinationPath $DOWNLOADDIR
-          Set-PSDebug -Trace 1
-          $HAXEPATH = Get-ChildItem $DOWNLOADDIR/haxe_*_* -Directory
-          Write-Host "::add-path::$HAXEPATH"
-          Write-Host "::set-env name=HAXELIB_ROOT::$HAXEPATH\lib"
-
-      - name: Print Haxe version
-        shell: pwsh
-        run: haxe -version
-
-      - name: "Make Python 3 be available as python3 in the cmdline"
-        shell: pwsh
-        run: |
-          Set-PSDebug -Trace 1
-          $pypath = python -c "import sys; print(sys.executable)"
-          $py3path = $pypath.replace("python.exe","python3.exe")
-          cmd /c mklink $py3path $pypath
-          python3 -V
-
-      - name: Install hererocks
-        if: matrix.target == 'lua'
-        shell: cmd
-        run: |
-          pip install hererocks
-          hererocks lua53 -l5.3 -rlatest
-          call lua53/bin/activate
-
-      - name: Install wget
-        if: matrix.target == 'flash'
-        shell: cmd
-        run: |
-          choco install wget
-          wget --version
-
-      - name: Setup haxelib
-        shell: pwsh
-        run: |
-          mkdir "$env:HAXELIB_ROOT"
-          haxelib setup "$env:HAXELIB_ROOT"
-
-      - name: Test
-        shell: pwsh
-        run: haxe RunCi.hxml
-        working-directory: ${{github.workspace}}/tests
-
-
   mac-test:
     needs: mac-build
     runs-on: macos-latest
@@ -881,7 +650,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        target: [macro, js, hl, cpp, jvm, php, python, flash, neko]
+        target: [macro, js, hl, cpp, jvm, php, python, lua, flash, neko]
         include:
           - target: hl
             BREW_PACKAGES: ninja
@@ -947,7 +716,7 @@ jobs:
 
   deploy:
     if: success() && github.repository_owner == 'HaxeFoundation' && github.event_name != 'pull_request'
-    needs: [linux-test, linux-arm64, mac-test, windows-test, windows64-test]
+    needs: [linux-test, linux-arm64, mac-test, windows64-test]
     runs-on: ubuntu-20.04
     steps:
       # this is only needed for to get `COMMIT_DATE`...
@@ -990,9 +759,6 @@ jobs:
           aws s3 cp win64Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows64/${FILE_NAME}.zip
           aws s3 cp win64Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows64-installer/${FILE_NAME}.zip
           aws s3 cp win64Binaries/*.nupkg           ${HXBUILDS_S3ADDR}/haxe/windows64-choco/
-          aws s3 cp win32Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows/${FILE_NAME}.zip
-          aws s3 cp win32Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows-installer/${FILE_NAME}.zip
-          aws s3 cp win32Binaries/*.nupkg           ${HXBUILDS_S3ADDR}/haxe/windows-choco/
 
       - name: Update "latest"
         if: github.ref == 'refs/heads/development'
@@ -1010,20 +776,17 @@ jobs:
           aws s3 cp macBinaries/*_installer.tar.gz  ${HXBUILDS_S3ADDR}/haxe/mac-installer/haxe_latest.tar.gz
           aws s3 cp win64Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows64/haxe_latest.zip
           aws s3 cp win64Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows64-installer/haxe_latest.zip
-          aws s3 cp win32Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows/haxe_latest.zip
-          aws s3 cp win32Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows-installer/haxe_latest.zip
 
           # Chocolatey packages have to be named with version number,
           # so let's use web redirection to keep the original file name.
           [[ "$HXBUILDS_S3ADDR" =~ s3://([^/]+)(.*) ]] && HXBUILDS_S3BUCKET="${BASH_REMATCH[1]}" && HXBUILDS_S3PATH="${BASH_REMATCH[2]}"
           [[ `echo win64Binaries/*.nupkg` =~ win64Binaries/(.+) ]] && FILE_NAME="${BASH_REMATCH[1]}"
           aws s3 cp ${HXBUILDS_S3ADDR}/haxe/windows64-choco/${FILE_NAME} ${HXBUILDS_S3ADDR}/haxe/windows64-choco/haxe_latest.nupkg --acl public-read --website-redirect "${HXBUILDS_S3PATH}/haxe/windows64-choco/${FILE_NAME}"
-          [[ `echo win32Binaries/*.nupkg` =~ win32Binaries/(.+) ]] && FILE_NAME="${BASH_REMATCH[1]}"
-          aws s3 cp ${HXBUILDS_S3ADDR}/haxe/windows-choco/${FILE_NAME}   ${HXBUILDS_S3ADDR}/haxe/windows-choco/haxe_latest.nupkg   --acl public-read --website-redirect "${HXBUILDS_S3PATH}/haxe/windows-choco/${FILE_NAME}"
+
 
   deploy_apidoc:
     if: success() && github.repository_owner == 'HaxeFoundation' && github.event_name != 'pull_request'
-    needs: [linux-test, linux-arm64, mac-test, windows-test, windows64-test]
+    needs: [linux-test, linux-arm64, mac-test, windows64-test]
     runs-on: ubuntu-20.04
     steps:
       - name: Install dependencies

+ 4 - 61
extra/github-actions/workflows/main.yml

@@ -28,30 +28,6 @@ jobs:
       @import install-ocaml-libs-windows.yml
       @import build-windows.yml
 
-  windows-build:
-    runs-on: windows-latest
-    env:
-      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
-      PLATFORM: windows
-      ARCH: 32
-      MINGW_ARCH: i686
-      CYG_ROOT: D:\cygwin
-    steps:
-      - uses: actions/checkout@main
-        with:
-          submodules: recursive
-
-      - name: Use GNU Tar from msys
-        run: |
-          echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
-          rm C:\msys64\usr\bin\bash.exe
-
-      @import install-nsis.yml
-      @import install-neko-windows.yml
-      @import install-ocaml-windows.yml
-      @import install-ocaml-libs-windows.yml
-      @import build-windows.yml
-
   linux-build:
     runs-on: ubuntu-20.04
     env:
@@ -375,33 +351,6 @@ jobs:
       @import install-neko-windows.yml
       @import test-windows.yml
 
-  windows-test:
-    needs: windows-build
-    runs-on: windows-latest
-    env:
-      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
-      PLATFORM: windows
-      TEST: ${{matrix.target}}
-      HXCPP_COMPILE_CACHE: ~/hxcache
-      ARCH: 32
-    strategy:
-      fail-fast: false
-      matrix:
-        # TODO jvm: https://github.com/HaxeFoundation/haxe/issues/8601
-        # TODO enable lua after https://github.com/HaxeFoundation/haxe/issues/10919
-        target: [macro, js, hl, cpp, php, python, flash, neko]
-    steps:
-      - uses: actions/checkout@main
-        with:
-          submodules: recursive
-      - uses: actions/download-artifact@v3
-        with:
-          name: win${{env.ARCH}}Binaries
-          path: win${{env.ARCH}}Binaries
-
-      @import install-neko-windows.yml
-      @import test-windows.yml
-
   mac-test:
     needs: mac-build
     runs-on: macos-latest
@@ -413,7 +362,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        target: [macro, js, hl, cpp, jvm, php, python, flash, neko]
+        target: [macro, js, hl, cpp, jvm, php, python, lua, flash, neko]
         include:
           - target: hl
             BREW_PACKAGES: ninja
@@ -431,7 +380,7 @@ jobs:
 
   deploy:
     if: success() && github.repository_owner == 'HaxeFoundation' && github.event_name != 'pull_request'
-    needs: [linux-test, linux-arm64, mac-test, windows-test, windows64-test]
+    needs: [linux-test, linux-arm64, mac-test, windows64-test]
     runs-on: ubuntu-20.04
     steps:
       # this is only needed for to get `COMMIT_DATE`...
@@ -474,9 +423,6 @@ jobs:
           aws s3 cp win64Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows64/${FILE_NAME}.zip
           aws s3 cp win64Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows64-installer/${FILE_NAME}.zip
           aws s3 cp win64Binaries/*.nupkg           ${HXBUILDS_S3ADDR}/haxe/windows64-choco/
-          aws s3 cp win32Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows/${FILE_NAME}.zip
-          aws s3 cp win32Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows-installer/${FILE_NAME}.zip
-          aws s3 cp win32Binaries/*.nupkg           ${HXBUILDS_S3ADDR}/haxe/windows-choco/
 
       - name: Update "latest"
         if: github.ref == 'refs/heads/development'
@@ -494,20 +440,17 @@ jobs:
           aws s3 cp macBinaries/*_installer.tar.gz  ${HXBUILDS_S3ADDR}/haxe/mac-installer/haxe_latest.tar.gz
           aws s3 cp win64Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows64/haxe_latest.zip
           aws s3 cp win64Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows64-installer/haxe_latest.zip
-          aws s3 cp win32Binaries/*_bin.zip         ${HXBUILDS_S3ADDR}/haxe/windows/haxe_latest.zip
-          aws s3 cp win32Binaries/*_installer.zip   ${HXBUILDS_S3ADDR}/haxe/windows-installer/haxe_latest.zip
 
           # Chocolatey packages have to be named with version number,
           # so let's use web redirection to keep the original file name.
           [[ "$HXBUILDS_S3ADDR" =~ s3://([^/]+)(.*) ]] && HXBUILDS_S3BUCKET="${BASH_REMATCH[1]}" && HXBUILDS_S3PATH="${BASH_REMATCH[2]}"
           [[ `echo win64Binaries/*.nupkg` =~ win64Binaries/(.+) ]] && FILE_NAME="${BASH_REMATCH[1]}"
           aws s3 cp ${HXBUILDS_S3ADDR}/haxe/windows64-choco/${FILE_NAME} ${HXBUILDS_S3ADDR}/haxe/windows64-choco/haxe_latest.nupkg --acl public-read --website-redirect "${HXBUILDS_S3PATH}/haxe/windows64-choco/${FILE_NAME}"
-          [[ `echo win32Binaries/*.nupkg` =~ win32Binaries/(.+) ]] && FILE_NAME="${BASH_REMATCH[1]}"
-          aws s3 cp ${HXBUILDS_S3ADDR}/haxe/windows-choco/${FILE_NAME}   ${HXBUILDS_S3ADDR}/haxe/windows-choco/haxe_latest.nupkg   --acl public-read --website-redirect "${HXBUILDS_S3PATH}/haxe/windows-choco/${FILE_NAME}"
+
 
   deploy_apidoc:
     if: success() && github.repository_owner == 'HaxeFoundation' && github.event_name != 'pull_request'
-    needs: [linux-test, linux-arm64, mac-test, windows-test, windows64-test]
+    needs: [linux-test, linux-arm64, mac-test, windows64-test]
     runs-on: ubuntu-20.04
     steps:
       - name: Install dependencies

+ 6 - 0
src-json/meta.json

@@ -342,6 +342,12 @@
 		"targets": ["TAbstractField"],
 		"links": ["https://haxe.org/manual/types-abstract-implicit-casts.html"]
 	},
+	{
+		"name": "FunctionalInterface",
+		"metadata": ":functionalInterface",
+		"doc": "Mark an interface as a functional interface",
+		"platforms": ["jvm"]
+	},
 	{
 		"name": "FunctionCode",
 		"metadata": ":functionCode",

+ 10 - 0
src/codegen/javaModern.ml

@@ -993,6 +993,16 @@ module Converter = struct
 			in
 			add_meta (Meta.Annotation,args,p)
 		end;
+		List.iter (fun attr -> match attr with
+			| AttrVisibleAnnotations ann ->
+				List.iter (function
+					| { ann_type = TObject( (["java";"lang"], "FunctionalInterface"), [] ) } ->
+						add_meta (Meta.FunctionalInterface,[],p);
+					| _ -> ()
+				) ann
+			| _ ->
+				()
+		) jc.jc_attributes;
 		let d = {
 			d_name = (class_name,p);
 			d_doc = None;

+ 12 - 6
src/compiler/hxb/hxbData.ml

@@ -10,10 +10,10 @@ exception HxbFailure of string
 	EN = enum
 	AB = abstract
 	TD = typedef
-	AN = anon
+	OB = anonymous object
 	CF = class field
 	EF = enum field
-	AF = anon field
+	OF = object field
 	EX = expression
 	EO = end of (Types | Fields | Module)
 	..F = forward definition
@@ -32,8 +32,10 @@ type chunk_kind =
 	| ENR (* enum references *)
 	| ABR (* abstract references *)
 	| TDR (* typedef references *)
-	(* Field references *)
-	| AFR (* anon field references *)
+	(* Anonymous objects *)
+	| OFR (* object field references *)
+	| OFD (* object field definitions *)
+	| OBD (* object definitions *)
 	(* Own module type definitions *)
 	| CLD (* class definition *)
 	| END (* enum definition *)
@@ -71,7 +73,9 @@ let string_of_chunk_kind = function
 	| ENR -> "ENR"
 	| ABR -> "ABR"
 	| TDR -> "TDR"
-	| AFR -> "AFR"
+	| OFR -> "OFR"
+	| OFD -> "OFD"
+	| OBD -> "OBD"
 	| EFR -> "EFR"
 	| CFR -> "CFR"
 	| CLD -> "CLD"
@@ -96,7 +100,9 @@ let chunk_kind_of_string = function
 	| "ENR" -> ENR
 	| "ABR" -> ABR
 	| "TDR" -> TDR
-	| "AFR" -> AFR
+	| "OFR" -> OFR
+	| "OFD" -> OFD
+	| "OBD" -> OBD
 	| "EFR" -> EFR
 	| "CFR" -> CFR
 	| "CLD" -> CLD

+ 25 - 7
src/compiler/hxb/hxbReader.ml

@@ -301,7 +301,8 @@ class hxb_reader
 			anons.(read_uleb128 ch)
 		| 1 ->
 			let an = anons.(read_uleb128 ch) in
-			self#read_anon an
+			self#read_anon an;
+			an
 		| _ ->
 			assert false
 
@@ -1587,11 +1588,26 @@ class hxb_reader
 		) in
 		enum_fields <- a
 
-	method read_afr =
+	method read_ofr =
 		let l = read_uleb128 ch in
 		let a = Array.init l (fun _ -> self#read_class_field_forward) in
 		anon_fields <- a
 
+	method read_ofd =
+		let l = read_uleb128 ch in
+		for _ = 0 to l - 1 do
+			let index = read_uleb128 ch in
+			let cf = anon_fields.(index) in
+			self#read_class_field_and_overloads_data cf;
+		done
+
+	method read_obd =
+		let l = read_uleb128 ch in
+		for _ = 0 to l - 1 do
+			let index = read_uleb128 ch in
+			self#read_anon anons.(index)
+		done
+
 	method read_cfr =
 		let l = read_uleb128 ch in
 		let a = Array.init l (fun i ->
@@ -1748,9 +1764,7 @@ class hxb_reader
 			an.a_status := Extend self#read_types;
 			read_fields ()
 		| _ -> assert false
-		end;
-
-		an
+		end
 
 	method read_tdd =
 		let l = read_uleb128 ch in
@@ -1926,8 +1940,12 @@ class hxb_reader
 			self#read_abr;
 		| TDR ->
 			self#read_tdr;
-		| AFR ->
-			self#read_afr;
+		| OFR ->
+			self#read_ofr;
+		| OFD ->
+			self#read_ofd;
+		| OBD ->
+			self#read_obd
 		| CLD ->
 			self#read_cld;
 		| END ->

+ 88 - 21
src/compiler/hxb/hxbWriter.ml

@@ -453,8 +453,8 @@ type hxb_writer = {
 	enums : (path,tenum) Pool.t;
 	typedefs : (path,tdef) Pool.t;
 	abstracts : (path,tabstract) Pool.t;
-	anons : (path,tanon) Pool.t;
-	anon_fields : (string,tclass_field,unit) HashedIdentityPool.t;
+	anons : (path,bytes option) Pool.t;
+	anon_fields : (string,tclass_field,bytes option) HashedIdentityPool.t;
 	tmonos : (tmono,unit) IdentityPool.t;
 
 	own_classes : (path,tclass) Pool.t;
@@ -468,6 +468,7 @@ type hxb_writer = {
 	mutable field_type_parameters : (typed_type_param,unit) IdentityPool.t;
 	mutable local_type_parameters : (typed_type_param,unit) IdentityPool.t;
 	mutable field_stack : unit list;
+	mutable wrote_local_type_param : bool;
 	unbound_ttp : (typed_type_param,unit) IdentityPool.t;
 	t_instance_chunk : Chunk.t;
 }
@@ -485,7 +486,7 @@ module HxbWriter = struct
 			| EOT | EOF | EOM -> 0
 			| MDF -> 16
 			| MTF | MDR | CLR | END | ABD | ENR | ABR | TDR | EFR | CFR | AFD -> 64
-			| AFR | CLD | TDD | EFD -> 128
+			| OFR | OFD | OBD | CLD | TDD | EFD -> 128
 			| STR | DOC -> 256
 			| CFD | EXD -> 512
 		in
@@ -1002,12 +1003,13 @@ module HxbWriter = struct
 		write_metadata writer v.v_meta;
 		write_pos writer v.v_pos
 
-	let rec write_anon writer (an : tanon) (ttp : type_params) =
+	let rec write_anon writer (an : tanon) =
+		let needs_local_context = ref false in
 		let write_fields () =
 			let restore = start_temporary_chunk writer 256 in
 			let i = ref 0 in
 			PMap.iter (fun _ cf ->
-				write_anon_field_ref writer cf;
+				write_anon_field_ref writer needs_local_context cf;
 				incr i;
 			) an.a_fields;
 			let bytes = restore (fun new_chunk -> Chunk.get_bytes new_chunk) in
@@ -1031,30 +1033,56 @@ module HxbWriter = struct
 			assert false
 		| AbstractStatics _ ->
 			assert false
-		end
+		end;
+		!needs_local_context
 
-	and write_anon_ref writer (an : tanon) (ttp : type_params) =
+	and write_anon_ref writer (an : tanon) =
 		let pfm = Option.get (writer.anon_id#identify_anon ~strict:true an) in
 		try
 			let index = Pool.get writer.anons pfm.pfm_path in
 			Chunk.write_u8 writer.chunk 0;
 			Chunk.write_uleb128 writer.chunk index
 		with Not_found ->
-			let index = Pool.add writer.anons pfm.pfm_path an in
-			Chunk.write_u8 writer.chunk 1;
-			Chunk.write_uleb128 writer.chunk index;
-			write_anon writer an ttp
+			let restore = start_temporary_chunk writer 256 in
+			let needs_local_context = write_anon writer an in
+			let bytes = restore (fun new_chunk -> Chunk.get_bytes new_chunk) in
+			if needs_local_context then begin
+				let index = Pool.add writer.anons pfm.pfm_path None in
+				Chunk.write_u8 writer.chunk 1;
+				Chunk.write_uleb128 writer.chunk index;
+				Chunk.write_bytes writer.chunk bytes
+			end else begin
+				let index = Pool.add writer.anons pfm.pfm_path (Some bytes) in
+				Chunk.write_u8 writer.chunk 0;
+				Chunk.write_uleb128 writer.chunk index;
+			end
 
-	and write_anon_field_ref writer cf =
+	and write_anon_field_ref writer needs_local_context cf =
 		try
 			let index = HashedIdentityPool.get writer.anon_fields cf.cf_name cf in
 			Chunk.write_u8 writer.chunk 0;
 			Chunk.write_uleb128 writer.chunk index
 		with Not_found ->
-			let index = HashedIdentityPool.add writer.anon_fields cf.cf_name cf () in
-			Chunk.write_u8 writer.chunk 1;
-			Chunk.write_uleb128 writer.chunk index;
-			ignore(write_class_field_and_overloads_data writer true cf)
+			let restore = start_temporary_chunk writer 256 in
+			let old = writer.wrote_local_type_param in
+			writer.wrote_local_type_param <- false;
+			ignore(write_class_field_and_overloads_data writer true cf);
+			let wrote_local_type_param = writer.wrote_local_type_param in
+			writer.wrote_local_type_param <- old;
+			let bytes = restore (fun new_chunk -> Chunk.get_bytes new_chunk) in
+			if wrote_local_type_param then begin
+				(* If we access something from the method scope, we have to write the anon field immediately.
+				   This should be fine because in such cases the field cannot be referenced elsewhere. *)
+				let index = HashedIdentityPool.add writer.anon_fields cf.cf_name cf None in
+				needs_local_context := true;
+				Chunk.write_u8 writer.chunk 1;
+				Chunk.write_uleb128 writer.chunk index;
+				Chunk.write_bytes writer.chunk bytes
+			end else begin
+				let index = HashedIdentityPool.add writer.anon_fields cf.cf_name cf (Some bytes) in
+				Chunk.write_u8 writer.chunk 0;
+				Chunk.write_uleb128 writer.chunk index;
+			end
 
 	(* Type instances *)
 
@@ -1063,14 +1091,18 @@ module HxbWriter = struct
 			begin match ttp.ttp_host with
 			| TPHType ->
 				let i = Pool.get writer.type_type_parameters ttp.ttp_name in
+				(* TODO: this isn't correct, but if we don't do this we'll have to communicate the current class *)
+				writer.wrote_local_type_param <- true;
 				Chunk.write_u8 writer.chunk 1;
 				Chunk.write_uleb128 writer.chunk i
 			| TPHMethod | TPHEnumConstructor | TPHAnonField | TPHConstructor ->
 				let i = IdentityPool.get writer.field_type_parameters ttp in
+				writer.wrote_local_type_param <- true;
 				Chunk.write_u8 writer.chunk 2;
 				Chunk.write_uleb128 writer.chunk i;
 			| TPHLocal ->
 				let index = IdentityPool.get writer.local_type_parameters ttp in
+				writer.wrote_local_type_param <- true;
 				Chunk.write_u8 writer.chunk 3;
 				Chunk.write_uleb128 writer.chunk index;
 		end with Not_found ->
@@ -1239,7 +1271,7 @@ module HxbWriter = struct
 				Chunk.write_u8 writer.chunk 80;
 			| TAnon an ->
 				Chunk.write_u8 writer.chunk 81;
-				write_anon_ref writer an []
+				write_anon_ref writer an
 			| TDynamic (Some t) ->
 				Chunk.write_u8 writer.chunk 89;
 				write_type_instance writer t
@@ -1532,7 +1564,7 @@ module HxbWriter = struct
 			| TField(e1,FAnon cf) ->
 				Chunk.write_u8 writer.chunk 104;
 				loop e1;
-				write_anon_field_ref writer cf;
+				write_anon_field_ref writer (ref false) cf;
 				true;
 			| TField(e1,FClosure(Some(c,tl),cf)) ->
 				Chunk.write_u8 writer.chunk 105;
@@ -1544,7 +1576,7 @@ module HxbWriter = struct
 			| TField(e1,FClosure(None,cf)) ->
 				Chunk.write_u8 writer.chunk 106;
 				loop e1;
-				write_anon_field_ref writer cf;
+				write_anon_field_ref writer (ref false) cf;
 				true;
 			| TField(e1,FEnum(en,ef)) ->
 				Chunk.write_u8 writer.chunk 107;
@@ -2121,11 +2153,28 @@ module HxbWriter = struct
 
 		let items = HashedIdentityPool.finalize writer.anon_fields in
 		if DynArray.length items > 0 then begin
-			start_chunk writer AFR;
+			start_chunk writer OFR;
 			Chunk.write_uleb128 writer.chunk (DynArray.length items);
 			DynArray.iter (fun (cf,_) ->
 				write_class_field_forward writer cf
 			) items;
+
+			let anon_fields_with_expr = DynArray.create () in
+			DynArray.iteri (fun i (_,bytes) -> match bytes with
+				| None ->
+					()
+				| Some bytes ->
+					DynArray.add anon_fields_with_expr (i,bytes)
+			) items;
+			if DynArray.length anon_fields_with_expr > 0 then begin
+				start_chunk writer OFD;
+				Chunk.write_uleb128 writer.chunk (DynArray.length anon_fields_with_expr);
+				DynArray.iter (fun (index,bytes) ->
+					Chunk.write_uleb128 writer.chunk index;
+					Chunk.write_bytes writer.chunk bytes
+				) anon_fields_with_expr
+			end;
+
 		end;
 
 		let items = Pool.finalize writer.classes in
@@ -2164,9 +2213,26 @@ module HxbWriter = struct
 		start_chunk writer MDF;
 		write_path writer m.m_path;
 		Chunk.write_string writer.chunk (Path.UniqueKey.lazy_path m.m_extra.m_file);
-		Chunk.write_uleb128 writer.chunk (DynArray.length (Pool.finalize writer.anons));
+		let anons = Pool.finalize writer.anons in
+		Chunk.write_uleb128 writer.chunk (DynArray.length anons);
 		Chunk.write_uleb128 writer.chunk (DynArray.length (IdentityPool.finalize writer.tmonos));
 
+		let anons_without_context = DynArray.create () in
+		DynArray.iteri (fun i bytes -> match bytes with
+			| None ->
+				()
+			| Some bytes ->
+				DynArray.add anons_without_context (i,bytes)
+		) anons;
+		if DynArray.length anons_without_context > 0 then begin
+			start_chunk writer OBD;
+			Chunk.write_uleb128 writer.chunk (DynArray.length anons_without_context);
+			DynArray.iter (fun (i,bytes) ->
+				Chunk.write_uleb128 writer.chunk i;
+				Chunk.write_bytes writer.chunk bytes
+			) anons_without_context
+		end;
+
 		begin
 			let deps = DynArray.create () in
 			PMap.iter (fun _ mdep ->
@@ -2244,6 +2310,7 @@ let create config warn anon_id =
 		field_type_parameters = IdentityPool.create ();
 		local_type_parameters = IdentityPool.create ();
 		field_stack = [];
+		wrote_local_type_param = false;
 		unbound_ttp = IdentityPool.create ();
 		t_instance_chunk = Chunk.create EOM cp 32;
 	}

+ 4 - 2
src/compiler/server.ml

@@ -389,6 +389,7 @@ let check_module sctx com m_path m_extra p =
 class hxb_reader_api_server
 	(com : Common.context)
 	(cc : context_cache)
+	(delay : (unit -> unit) -> unit)
 = object(self)
 
 	method make_module (path : path) (file : string) =
@@ -426,7 +427,8 @@ class hxb_reader_api_server
 			(* We try to avoid reading expressions as much as possible, so we only do this for
 				 our current display file if we're in display mode. *)
 			let is_display_file = DisplayPosition.display_position#is_in_file (Path.UniqueKey.lazy_key m.m_extra.m_file) in
-			if is_display_file || com.display.dms_full_typing then ignore(f_next chunks EOM);
+			if is_display_file || com.display.dms_full_typing then ignore(f_next chunks EOM)
+			else delay (fun () -> ignore(f_next chunks EOM));
 			m
 		| BadModule reason ->
 			die (Printf.sprintf "Unexpected BadModule %s" (s_type_path path)) __LOC__
@@ -568,7 +570,7 @@ and type_module sctx com delay mpath p =
 						| Some api ->
 							api
 						| None ->
-							let api = (new hxb_reader_api_server com cc :> HxbReaderApi.hxb_reader_api) in
+							let api = (new hxb_reader_api_server com cc delay :> HxbReaderApi.hxb_reader_api) in
 							com.hxb_reader_api <- Some api;
 							api
 					in

+ 11 - 2
src/context/abstractCast.ml

@@ -87,10 +87,19 @@ and do_check_cast ctx uctx tleft eright p =
 						loop2 a.a_to
 					end
 				| TInst(c,tl), TFun _ when has_class_flag c CFunctionalInterface ->
-					let cf = ctx.g.functional_interface_lut#find c.cl_path in
+					let cf = try
+						snd (ctx.com.functional_interface_lut#find c.cl_path)
+					with Not_found -> match TClass.get_singular_interface_field c.cl_ordered_fields with
+						| None ->
+							raise Not_found
+						| Some cf ->
+							ctx.com.functional_interface_lut#add c.cl_path (c,cf);
+							cf
+					in
 					let map = apply_params c.cl_params tl in
 					let monos = Monomorph.spawn_constrained_monos map cf.cf_params in
-					unify_raise_custom uctx eright.etype (map (apply_params cf.cf_params monos cf.cf_type)) p;
+					unify_raise_custom native_unification_context eright.etype (map (apply_params cf.cf_params monos cf.cf_type)) p;
+					if has_mono tright then raise_typing_error ("Cannot use this function as a functional interface because it has unknown types: " ^ (s_type (print_context()) tright)) p;
 					eright
 				| _ ->
 					raise Not_found

+ 3 - 0
src/context/common.ml

@@ -407,6 +407,7 @@ type context = {
 	mutable modules : Type.module_def list;
 	mutable types : Type.module_type list;
 	mutable resources : (string,string) Hashtbl.t;
+	functional_interface_lut : (path,(tclass * tclass_field)) lookup;
 	(* target-specific *)
 	mutable flash_version : float;
 	mutable neko_lib_paths : string list;
@@ -846,6 +847,7 @@ let create compilation_step cs version args display_mode =
 		has_error = false;
 		report_mode = RMNone;
 		is_macro_context = false;
+		functional_interface_lut = new Lookup.hashtbl_lookup;
 		hxb_reader_api = None;
 		hxb_reader_stats = HxbReader.create_hxb_reader_stats ();
 		hxb_writer_config = None;
@@ -902,6 +904,7 @@ let clone com is_macro_context =
 		hxb_reader_api = None;
 		hxb_reader_stats = HxbReader.create_hxb_reader_stats ();
 		std = null_class;
+		functional_interface_lut = new Lookup.hashtbl_lookup;
 		empty_class_path = new ClassPath.directory_class_path "" User;
 		class_paths = new ClassPaths.class_paths;
 	}

+ 1 - 1
src/context/display/displayTexpr.ml

@@ -173,7 +173,7 @@ let check_display_file ctx cs =
 			let m = try
 				ctx.com.module_lut#find path
 			with Not_found ->
-				begin match !TypeloadCacheHook.type_module_hook ctx.com (delay ctx.g PTypeField) path null_pos with
+				begin match !TypeloadCacheHook.type_module_hook ctx.com (delay ctx.g PConnectField) path null_pos with
 				| NoModule | BadModule _ -> raise Not_found
 				| BinaryModule mc ->
 					let api = (new TypeloadModule.hxb_reader_api_typeload ctx.com ctx.g TypeloadModule.load_module' p :> HxbReaderApi.hxb_reader_api) in

+ 0 - 2
src/context/typecore.ml

@@ -20,7 +20,6 @@
 open Globals
 open Ast
 open Common
-open Lookup
 open Type
 open Error
 open Resolution
@@ -125,7 +124,6 @@ type typer_globals = {
 	mutable complete : bool;
 	mutable type_hints : (module_def_display * pos * t) list;
 	mutable load_only_cached_modules : bool;
-	functional_interface_lut : (path,tclass_field) lookup;
 	mutable return_partial_type : bool;
 	mutable build_count : int;
 	mutable t_dynamic_def : Type.t;

+ 20 - 0
src/core/tOther.ml

@@ -431,6 +431,26 @@ module TClass = struct
 			cf.cf_expr <- Some e;
 			c.cl_init <- Some cf
 
+	let get_singular_interface_field fields =
+		let is_normal_field cf =
+			not (has_class_field_flag cf CfDefault) && match cf.cf_kind with
+				| Method MethNormal -> true
+				| _ -> false
+		in
+		let rec loop o l = match l with
+			| cf :: l ->
+				if is_normal_field cf then begin
+					if o = None then
+						loop (Some cf) l
+					else
+						None
+				end else
+					loop o l
+			| [] ->
+				o
+		in
+		loop None fields
+
 	let add_cl_init c e =
 		modify_cl_init c e true
 

+ 11 - 0
src/core/tUnification.ml

@@ -66,6 +66,17 @@ let default_unification_context = {
 	strict_field_kind       = false;
 }
 
+(* Unify like targets (e.g. Java) probably would. *)
+let native_unification_context = {
+	allow_transitive_cast = false;
+	allow_abstract_cast   = false;
+	allow_dynamic_to_cast = false;
+	equality_kind         = EqStrict;
+	equality_underlying   = false;
+	allow_arg_name_mismatch = true;
+	strict_field_kind       = false;
+}
+
 module Monomorph = struct
 	let create () = {
 		tm_type = None;

+ 65 - 14
src/generators/genjvm.ml

@@ -60,9 +60,11 @@ type generation_context = {
 	t_exception : Type.t;
 	t_throwable : Type.t;
 	anon_identification : jsignature tanon_identification;
+	mutable functional_interfaces : (tclass * tclass_field * JvmFunctions.JavaFunctionalInterface.t) list;
 	mutable preprocessor : jsignature preprocessor;
 	default_export_config : export_config;
 	typed_functions : JvmFunctions.typed_functions;
+	known_typed_functions : (path,unit) Hashtbl.t;
 	closure_paths : (path * string * jsignature,path) Hashtbl.t;
 	enum_paths : (path,unit) Hashtbl.t;
 	detail_times : bool;
@@ -417,10 +419,37 @@ let generate_equals_function (jc : JvmClass.builder) jsig_arg =
 	save();
 	jm_equals,load
 
-let create_field_closure gctx jc path_this jm name jsig =
+let associate_functional_interfaces gctx f t =
+	if not (has_mono t) then begin
+		List.iter (fun (c,cf,jfi) ->
+			let c_monos = Monomorph.spawn_constrained_monos (fun t -> t) c.cl_params in
+			let map t = apply_params c.cl_params c_monos t in
+			let cf_monos = Monomorph.spawn_constrained_monos map cf.cf_params in
+			try
+				Type.unify_custom native_unification_context t (apply_params cf.cf_params cf_monos (map cf.cf_type));
+				ignore(List.map follow cf_monos);
+				f#add_functional_interface jfi (List.map (jsignature_of_type gctx) c_monos)
+			with Unify_error _ ->
+				()
+		) gctx.functional_interfaces
+	end
+
+let create_typed_function gctx kind jc jm context =
+	let wf = new JvmFunctions.typed_function gctx.typed_functions kind jc jm context in
+	let jc = wf#get_class in
+	Hashtbl.add gctx.known_typed_functions jc#get_this_path ();
+	wf
+
+let create_field_closure gctx jc path_this jm name jsig t =
 	let jsig_this = object_path_sig path_this in
 	let context = ["this",jsig_this] in
-	let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncMember(path_this,name)) jc jm context in
+	let wf = create_typed_function gctx (FuncMember(path_this,name)) jc jm context in
+	begin match t with
+		| None ->
+			()
+		| Some t ->
+			associate_functional_interfaces gctx wf t
+	end;
 	let jc_closure = wf#get_class in
 	ignore(wf#generate_constructor true);
 	let args,ret = match jsig with
@@ -461,12 +490,12 @@ let create_field_closure gctx jc path_this jm name jsig =
 	write_class gctx jc_closure#get_this_path (jc_closure#export_class gctx.default_export_config);
 	jc_closure#get_this_path
 
-let create_field_closure gctx jc path_this jm name jsig f =
+let create_field_closure gctx jc path_this jm name jsig f t =
 	let jsig_this = object_path_sig path_this in
 	let closure_path = try
 		Hashtbl.find gctx.closure_paths (path_this,name,jsig)
 	with Not_found ->
-		let closure_path = create_field_closure gctx jc path_this jm name jsig in
+		let closure_path = create_field_closure gctx jc path_this jm name jsig t in
 		Hashtbl.add gctx.closure_paths (path_this,name,jsig) closure_path;
 		closure_path
 	in
@@ -575,7 +604,8 @@ class texpr_to_jvm
 			| RValue(_,Some s) -> Some s
 			| _ -> None
 		in
-		let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncLocal name) jc jm context in
+		let wf = create_typed_function gctx (FuncLocal name) jc jm context in
+		associate_functional_interfaces gctx wf e.etype;
 		let jc_closure = wf#get_class in
 		ignore(wf#generate_constructor (env <> []));
 		let filter = match ret with
@@ -659,12 +689,13 @@ class texpr_to_jvm
 		| None ->
 			default();
 
-	method read_static_closure (path : path) (name : string) (args : (string * jsignature) list) (ret : jsignature option) =
+	method read_static_closure (path : path) (name : string) (args : (string * jsignature) list) (ret : jsignature option) (t : Type.t) =
 		let jsig = method_sig (List.map snd args) ret in
 		let closure_path = try
 			Hashtbl.find gctx.closure_paths (path,name,jsig)
 		with Not_found ->
-			let wf = new JvmFunctions.typed_function gctx.typed_functions (FuncStatic(path,name)) jc jm [] in
+			let wf = create_typed_function gctx (FuncStatic(path,name)) jc jm [] in
+			associate_functional_interfaces gctx wf t;
 			let jc_closure = wf#get_class in
 			ignore(wf#generate_constructor false);
 			let jm_invoke = wf#generate_invoke args ret [] in
@@ -691,7 +722,7 @@ class texpr_to_jvm
 				| TFun(tl,tr) -> List.map (fun (n,_,t) -> n,self#vtype t) tl,(return_of_type gctx tr)
 				| _ -> die "" __LOC__
 			in
-			self#read_static_closure path cf.cf_name args ret
+			self#read_static_closure path cf.cf_name args ret cf.cf_type
 		in
 		let dynamic_read s =
 			self#texpr rvalue_any e1;
@@ -738,7 +769,7 @@ class texpr_to_jvm
 			else
 				create_field_closure gctx jc c.cl_path jm cf.cf_name (self#vtype cf.cf_type) (fun () ->
 					self#texpr rvalue_any e1;
-				)
+				) (Some cf.cf_type)
 
 	method read_write ret ak e (f : unit -> unit) =
 		let apply dup =
@@ -1017,6 +1048,15 @@ class texpr_to_jvm
 	method get_binop_type t1 t2 = self#get_binop_type_sig (jsignature_of_type gctx t1) (jsignature_of_type gctx t2)
 
 	method do_compare op =
+		let fun_compare path1 sig2 = match sig2 with
+			| TObject(path2,_) when path1 = path2 ->
+				jm#invokevirtual path2 "equals" (method_sig [object_sig] (Some TBool));
+				CmpNormal(op,TBool)
+			| _ ->
+				jm#invokestatic haxe_jvm_path "compare" (method_sig [object_sig;object_sig] (Some TInt));
+				let op = flip_cmp_op op in
+				CmpNormal(op,TBool)
+		in
 		match code#get_stack#get_stack_items 2 with
 		| [TInt | TByte | TChar | TShort | TBool;TInt | TByte | TChar | TShort | TBool] ->
 			let op = flip_cmp_op op in
@@ -1030,6 +1070,11 @@ class texpr_to_jvm
 			jm#invokestatic haxe_jvm_path "compare" (method_sig [object_sig;object_sig] (Some TInt));
 			let op = flip_cmp_op op in
 			CmpNormal(op,TBool)
+		| [sig2;TObject(path1,_)] when Hashtbl.mem gctx.known_typed_functions path1 ->
+			fun_compare path1 sig2
+		| [TObject(path1,_);sig2] when Hashtbl.mem gctx.known_typed_functions path1 ->
+			code#swap;
+			fun_compare path1 sig2
 		| [(TObject _ | TArray _ | TMethod _) as t1;(TObject _ | TArray _ | TMethod _) as t2] ->
 			CmpSpecial ((if op = CmpEq then code#if_acmp_ne else code#if_acmp_eq) t1 t2)
 		| [TDouble;TDouble] ->
@@ -2209,7 +2254,7 @@ let generate_dynamic_access gctx (jc : JvmClass.builder) fields is_anon =
 			begin match kind,jsig with
 				| Method (MethNormal | MethInline),TMethod(args,_) ->
 					if gctx.dynamic_level >= 2 then begin
-						create_field_closure gctx jc jc#get_this_path jm name jsig (fun () -> jm#load_this)
+						create_field_closure gctx jc jc#get_this_path jm name jsig (fun () -> jm#load_this) None
 					end else begin
 						jm#load_this;
 						jm#string name;
@@ -2942,7 +2987,7 @@ module Preprocessor = struct
 		end else if fst mt.mt_path = [] then
 			mt.mt_path <- make_root mt.mt_path
 
-	let check_single_method_interface gctx c =
+	let check_functional_interface gctx c =
 		let rec loop m l = match l with
 			| [] ->
 				m
@@ -2961,7 +3006,8 @@ module Preprocessor = struct
 		| Some cf ->
 			match jsignature_of_type gctx cf.cf_type with
 			| TMethod(args,ret) ->
-				JvmFunctions.JavaFunctionalInterfaces.add args ret c.cl_path cf.cf_name (List.map extract_param_name (c.cl_params @ cf.cf_params));
+				let jfi = JvmFunctions.JavaFunctionalInterface.create args ret c.cl_path cf.cf_name (List.map extract_param_name (c.cl_params @ cf.cf_params)) in
+				gctx.functional_interfaces <- (c,cf,jfi) :: gctx.functional_interfaces;
 			| _ ->
 				()
 
@@ -2993,8 +3039,10 @@ module Preprocessor = struct
 		List.iter (fun mt ->
 			match mt with
 			| TClassDecl c ->
-				if not (has_class_flag c CInterface) then gctx.preprocessor#preprocess_class c
-				else check_single_method_interface gctx c;
+				if not (has_class_flag c CInterface) then
+					gctx.preprocessor#preprocess_class c
+				else if has_class_flag c CFunctionalInterface then
+					check_functional_interface gctx c
 			| _ -> ()
 		) gctx.com.types;
 		(* find typedef-interface implementations *)
@@ -3061,6 +3109,7 @@ let generate jvm_flag com =
 		preprocessor = Obj.magic ();
 		typedef_interfaces = Obj.magic ();
 		typed_functions = new JvmFunctions.typed_functions;
+		known_typed_functions = Hashtbl.create 0;
 		closure_paths = Hashtbl.create 0;
 		enum_paths = Hashtbl.create 0;
 		default_export_config = {
@@ -3070,7 +3119,9 @@ let generate jvm_flag com =
 		timer = new Timer.timer ["generate";"java"];
 		jar_compression_level = compression_level;
 		dynamic_level = dynamic_level;
+		functional_interfaces = [];
 	} in
+	Hashtbl.add gctx.known_typed_functions haxe_function_path ();
 	gctx.preprocessor <- new preprocessor com.basic (jsignature_of_type gctx);
 	gctx.typedef_interfaces <- new typedef_interfaces gctx.preprocessor#get_infos anon_identification;
 	gctx.typedef_interfaces#add_interface_rewrite (["haxe";"root"],"Iterator") (["java";"util"],"Iterator") true;

+ 18 - 91
src/generators/jvm/jvmFunctions.ml

@@ -285,7 +285,7 @@ type typed_function_kind =
 	| FuncMember of jpath * string
 	| FuncStatic of jpath * string
 
-module JavaFunctionalInterfaces = struct
+module JavaFunctionalInterface = struct
 	type t = {
 		jargs: jsignature list;
 		jret : jsignature option;
@@ -302,9 +302,7 @@ module JavaFunctionalInterfaces = struct
 		"jparams",String.concat ", " jfi.jparams;
 	]
 
-	let java_functional_interfaces = DynArray.create ()
-
-	let add args ret path name params =
+	let create args ret path name params =
 		let jfi = {
 			jargs = args;
 			jret = ret;
@@ -312,83 +310,9 @@ module JavaFunctionalInterfaces = struct
 			jname = name;
 			jparams = params;
 		} in
-		DynArray.add java_functional_interfaces jfi
-
-	let unify jfi args ret =
-		let params = ref [] in
-		let rec unify jsig1 jsig2 = match jsig1,jsig2 with
-			| TObject _,TObject((["java";"lang"],"Object"),[]) ->
-				true
-			| TObject(path1,params1),TObject(path2,params2) ->
-				path1 = path2 &&
-				unify_params params1 params2
-			| TTypeParameter n,jsig
-			| jsig,TTypeParameter n ->
-				List.mem_assoc n !params || begin
-					params := (n,jsig) :: !params;
-					true
-				end
-			| _ ->
-				jsig1 = jsig2
-		and unify_params params1 params2 = match params1,params2 with
-			| [],_
-			| _,[] ->
-				(* Assume raw type, I guess? *)
-				true
-			| param1 :: params1,param2 :: params2 ->
-				match param1,param2 with
-				| TAny,_
-				| _,TAny ->
-					(* Is this correct in both directions? *)
-					unify_params params1 params2
-				| TType(_,jsig1),TType(_,jsig2) ->
-					(* TODO: wildcard? *)
-					unify jsig1 jsig2 && unify_params params1 params2
-		in
-		let rec loop want have = match want,have with
-			| [],[] ->
-				let params = List.map (fun s ->
-					try
-						TType(WNone,List.assoc s !params)
-					with Not_found ->
-						TAny
-				) jfi.jparams in
-				Some (jfi,params)
-			| want1 :: want,have1 :: have ->
-				if unify have1 want1 then loop want have
-				else None
-			| _ ->
-				None
-		in
-		match jfi.jret,ret with
-		| None,None ->
-			loop jfi.jargs args
-		| Some jsig1,Some jsig2 ->
-			if unify jsig2 jsig1 then loop jfi.jargs args
-			else None
-		| _ ->
-			None
-
-
-	let find_compatible args ret filter =
-		DynArray.fold_left (fun acc jfi ->
-			if filter = [] || List.mem jfi.jpath filter then begin
-				if jfi.jparams = [] then begin
-					if jfi.jargs = args && jfi.jret = ret then
-						(jfi,[]) :: acc
-					else
-						acc
-				end else match unify jfi args ret with
-					| Some x ->
-						x :: acc
-					| None ->
-						acc
-			end else
-				acc
-		) [] java_functional_interfaces
+		jfi
 end
 
-open JavaFunctionalInterfaces
 open JvmGlobals
 
 class typed_function
@@ -400,6 +324,8 @@ class typed_function
 
 = object(self)
 
+	val mutable functional_interfaces = []
+
 	val jc_closure =
 		let name = match kind with
 			| FuncLocal s ->
@@ -431,6 +357,10 @@ class typed_function
 		jm_ctor#return;
 		jm_ctor
 
+	method add_functional_interface (jfi : JavaFunctionalInterface.t) (params : jsignature list) =
+		let params = List.map (fun jsig -> TType(WNone,jsig)) params in
+		functional_interfaces <- (jfi,params) :: functional_interfaces
+
 	method generate_invoke (args : (string * jsignature) list) (ret : jsignature option) (functional_interface_filter : jpath list) =
 		let arg_sigs = List.map snd args in
 		let meth = functions#register_signature arg_sigs ret in
@@ -455,19 +385,16 @@ class typed_function
 				functions#make_forward_method jc_closure jm_invoke_next meth_from meth_to;
 			end
 		in
-		let check_functional_interfaces meth =
-			let l = JavaFunctionalInterfaces.find_compatible meth.dargs meth.dret functional_interface_filter in
-			List.iter (fun (jfi,params) ->
-				add_interface jfi.jpath params;
-				let msig = method_sig jfi.jargs jfi.jret in
-				if not (jc_closure#has_method jfi.jname msig) then begin
-					let jm_invoke_next = spawn_invoke_next jfi.jname msig false in
-					functions#make_forward_method_jsig jc_closure jm_invoke_next meth.name jfi.jargs jfi.jret meth.dargs meth.dret
-				end
-			) l
-		in
+		let open JavaFunctionalInterface in
+		List.iter (fun (jfi,params) ->
+			add_interface jfi.jpath params;
+			let msig = method_sig jfi.jargs jfi.jret in
+			if not (jc_closure#has_method jfi.jname msig) then begin
+				let jm_invoke_next = spawn_invoke_next jfi.jname msig false in
+				functions#make_forward_method_jsig jc_closure jm_invoke_next meth.name jfi.jargs jfi.jret meth.dargs meth.dret
+			end
+		) functional_interfaces;
 		let rec loop meth =
-			check_functional_interfaces meth;
 			begin match meth.next with
 			| Some meth_next ->
 				spawn_forward_function meth_next meth true;

+ 2 - 1
src/macro/eval/evalStdLib.ml

@@ -2250,9 +2250,10 @@ module StdString = struct
 		let str = this str in
 		let this = this vthis in
 		let i = default_int startIndex 0 in
+		let i = max 0 i in
 		try
 			if str.slength = 0 then
-				vint (max 0 (min i this.slength))
+				vint (min i this.slength)
 			else begin
 				let i =
 					if i >= this.slength then raise Not_found

+ 1 - 0
src/macro/eval/evalValue.ml

@@ -320,6 +320,7 @@ let rec equals a b = match a,b with
 	| VVector vv1,VVector vv2 -> vv1 == vv2
 	| VFunction(vf1,_),VFunction(vf2,_) -> vf1 == vf2
 	| VPrototype proto1,VPrototype proto2 -> proto1.ppath = proto2.ppath
+	| VFieldClosure(v1,f1),VFieldClosure(v2,f2) -> f1 == f2 && equals v1 v2
 	| VNativeString s1,VNativeString s2 -> s1 = s2
 	| VHandle h1,VHandle h2 -> same_handle h1 h2
 	| VLazy f1,_ -> equals (!f1()) b

+ 3 - 19
src/typing/typeloadFields.ml

@@ -1605,28 +1605,12 @@ let finalize_class cctx =
 	) cctx.delayed_expr
 
 let check_functional_interface ctx c =
-	let is_normal_field cf =
-		(* TODO: more? *)
-		not (has_class_field_flag cf CfDefault)
-	in
-	let rec loop o l = match l with
-		| cf :: l ->
-			if is_normal_field cf then begin
-				if o = None then
-					loop (Some cf) l
-				else
-					None
-			end else
-				loop o l
-		| [] ->
-			o
-	in
-	match loop None c.cl_ordered_fields with
+	match TClass.get_singular_interface_field c.cl_ordered_fields with
 	| None ->
 		()
 	| Some cf ->
 		add_class_flag c CFunctionalInterface;
-		ctx.g.functional_interface_lut#add c.cl_path cf
+		ctx.com.functional_interface_lut#add c.cl_path (c,cf)
 
 let init_class ctx_c cctx c p herits fields =
 	let com = ctx_c.com in
@@ -1749,7 +1733,7 @@ let init_class ctx_c cctx c p herits fields =
 			a.a_unops <- List.rev a.a_unops;
 			a.a_array <- List.rev a.a_array;
 		| None ->
-			if (has_class_flag c CInterface) && com.platform = Jvm then check_functional_interface ctx_c c;
+			if (has_class_flag c CFunctionalInterface) && com.platform = Jvm then check_functional_interface ctx_c c;
 	end;
 	c.cl_ordered_statics <- List.rev c.cl_ordered_statics;
 	c.cl_ordered_fields <- List.rev c.cl_ordered_fields;

+ 7 - 1
src/typing/typeloadModule.ml

@@ -318,6 +318,12 @@ module ModuleLevel = struct
 			| ((EClass d, p),TClassDecl c) ->
 				c.cl_params <- type_type_params ctx_m TPHType c.cl_path p d.d_params;
 				if Meta.has Meta.Generic c.cl_meta && c.cl_params <> [] then c.cl_kind <- KGeneric;
+				if Meta.has Meta.FunctionalInterface c.cl_meta then begin
+					if not (has_class_flag c CInterface) then
+						raise_typing_error "@:functionalInterface is only allowed on interfaces, as the name implies" c.cl_name_pos
+					else
+						add_class_flag c CFunctionalInterface
+				end;
 				if Meta.has Meta.GenericBuild c.cl_meta then begin
 					if ctx_m.com.is_macro_context then raise_typing_error "@:genericBuild cannot be used in macros" c.cl_pos;
 					c.cl_kind <- KGenericBuild d.d_data;
@@ -804,7 +810,7 @@ and load_module' com g m p =
 		com.module_lut#find m
 	with Not_found ->
 		(* Check cache *)
-		match !TypeloadCacheHook.type_module_hook com (delay g PTypeField) m p with
+		match !TypeloadCacheHook.type_module_hook com (delay g PConnectField) m p with
 		| GoodModule m ->
 			m
 		| BinaryModule _ ->

+ 0 - 1
src/typing/typerEntry.ml

@@ -27,7 +27,6 @@ let create com macros =
 			return_partial_type = false;
 			build_count = 0;
 			t_dynamic_def = t_dynamic;
-			functional_interface_lut = new Lookup.pmap_lookup;
 			do_macro = MacroContext.type_macro;
 			do_load_macro = MacroContext.load_macro';
 			do_load_module = TypeloadModule.load_module;

+ 10 - 8
std/String.hx

@@ -74,18 +74,20 @@ extern class String {
 	function charCodeAt(index:Int):Null<Int>;
 
 	/**
-		Returns the position of the leftmost occurrence of `str` within `this`
-		String.
+		Returns the position of the leftmost occurrence of `str` within `this` String.
 
-		If `startIndex` is given, the search is performed within the substring
-		of `this` String starting from `startIndex`.
+		If `str` is the empty String `""`, then:
+			* If `startIndex` is not specified or < 0, 0 is returned.
+			* If `startIndex >= this.length`, `this.length` is returned.
+			* Otherwise, `startIndex` is returned,
 
-		If `startIndex` exceeds `this.length`, -1 is returned.
+		Otherwise, if `startIndex` is not specified or < 0, it is treated as 0.
 
-		If `startIndex` is negative, the result is unspecifed.
+		If `startIndex >= this.length`, -1 is returned.
 
-		Otherwise the search is performed within `this` String. In either case,
-		the returned position is relative to the beginning of `this` String.
+		Otherwise the search is performed within the substring of `this` String starting
+		at `startIndex`. If `str` is found, the position of its first character in `this`
+		String relative to position 0 is returned.
 
 		If `str` cannot be found, -1 is returned.
 	**/

+ 9 - 17
std/UnicodeString.hx

@@ -188,27 +188,19 @@ abstract UnicodeString(String) from String to String {
 	}
 
 	/**
-		Returns the position of the leftmost occurrence of `str` within `this`
-		String.
-
-		If `startIndex` is given, the search is performed within the substring
-		of `this` String starting from `startIndex` (if `startIndex` is posivite
-		or 0) or `max(this.length + startIndex, 0)` (if `startIndex` is negative).
-
-		If `startIndex` exceeds `this.length`, -1 is returned.
-
-		Otherwise the search is performed within `this` String. In either case,
-		the returned position is relative to the beginning of `this` String.
-
-		If `str` cannot be found, -1 is returned.
+		@see String.indexOf
 	**/
 	public function indexOf(str:String, ?startIndex:Int):Int {
-		if (startIndex == null) {
-			startIndex = 0;
+		var startIndex:Int = if (startIndex == null || startIndex < 0) {
+			0;
 		} else {
-			if (startIndex < 0) {
-				startIndex = (this : UnicodeString).length + startIndex;
+			startIndex;
+		}
+		if (str.length == 0) {
+			if (startIndex > length) {
+				return length;
 			}
+			return startIndex;
 		}
 
 		var unicodeOffset = 0;

+ 5 - 2
std/jvm/StringExt.hx

@@ -48,12 +48,15 @@ class StringExt {
 	}
 
 	public static function indexOf(me:String, str:String, startIndex:Null<Int>) {
+		if (str.length == 0) {
+			return java.lang.Math.max(0, java.lang.Math.min(startIndex == null ? 0 : startIndex, me.length));
+		}
 		return if (startIndex == null) (cast me : NativeString).indexOf(str) else (cast me : NativeString).indexOf(str, startIndex);
 	}
 
 	public static function lastIndexOf(me:String, str:String, ?startIndex:Int):Int {
-		if(str == '') {
-			return startIndex == null || startIndex > me.length ? me.length : startIndex;
+		if (str.length == 0) {
+			return java.lang.Math.max(0, java.lang.Math.min(startIndex == null ? me.length : startIndex, me.length));
 		}
 		if (startIndex == null || startIndex > me.length || startIndex < 0) {
 			startIndex = me.length - 1;

+ 14 - 9
std/jvm/_std/Reflect.hx

@@ -21,7 +21,6 @@
  */
 
 import jvm.Jvm;
-
 import java.lang.Number;
 import java.lang.Long.LongClass;
 import java.lang.Double.DoubleClass;
@@ -110,24 +109,24 @@ class Reflect {
 			return 1;
 		}
 		if (Jvm.instanceof(a, Number) && Jvm.instanceof(b, Number)) {
-			var a = (cast a:Number);
-			var b = (cast b:Number);
+			var a = (cast a : Number);
+			var b = (cast b : Number);
 			inline function isBig(v:Number)
 				return Jvm.instanceof(v, BigDecimal) || Jvm.instanceof(v, BigInteger);
 			inline function cmpLongTo(long:Number, another:Number) {
-				if(Jvm.instanceof(another, DoubleClass)) {
+				if (Jvm.instanceof(another, DoubleClass)) {
 					return new BigDecimal(long.longValue()).compareTo(new BigDecimal(another.doubleValue()));
-				} else if(Jvm.instanceof(another, FloatClass)) {
+				} else if (Jvm.instanceof(another, FloatClass)) {
 					return new BigDecimal(long.longValue()).compareTo(new BigDecimal(another.floatValue()));
 				} else {
 					return LongClass.compare(long.longValue(), another.longValue());
 				}
 			}
-			if(isBig(a) || isBig(b))
-				return new BigDecimal((cast a:java.lang.Object).toString()).compareTo((cast a:java.lang.Object).toString());
-			if(Jvm.instanceof(a, LongClass))
+			if (isBig(a) || isBig(b))
+				return new BigDecimal((cast a : java.lang.Object).toString()).compareTo((cast a : java.lang.Object).toString());
+			if (Jvm.instanceof(a, LongClass))
 				return cmpLongTo(a, b);
-			if(Jvm.instanceof(b, LongClass))
+			if (Jvm.instanceof(b, LongClass))
 				return -1 * cmpLongTo(b, a);
 			return DoubleClass.compare(a.doubleValue(), b.doubleValue());
 		}
@@ -137,6 +136,12 @@ class Reflect {
 			}
 			return (cast a : java.NativeString).compareTo(cast b);
 		}
+		if (Jvm.instanceof(a, jvm.Function)) {
+			if (!(cast a : jvm.Function).equals(cast b)) {
+				return -1;
+			}
+			return 0;
+		}
 		return -1;
 	}
 

+ 19 - 12
std/lua/_std/String.hx

@@ -36,8 +36,7 @@ class String {
 
 	public var length(default, null):Int;
 
-	public inline function new(string:String)
-		untyped {}
+	public inline function new(string:String) untyped {}
 
 	@:keep
 	static function __index(s:Dynamic, k:Dynamic):Dynamic {
@@ -63,7 +62,7 @@ class String {
 		return BaseString.lower(this);
 
 	public function indexOf(str:String, ?startIndex:Int):Int {
-		if (startIndex == null)
+		if (startIndex == null || startIndex < 0)
 			startIndex = 1;
 		else
 			startIndex += 1;
@@ -79,9 +78,8 @@ class String {
 
 	static function indexOfEmpty(s:String, startIndex:Int):Int {
 		var length = BaseString.len(s);
-		if(startIndex < 0) {
-			startIndex = length + startIndex;
-			if(startIndex < 0) startIndex = 0;
+		if (startIndex < 0) {
+			startIndex = 0;
 		}
 		return startIndex > length ? length : startIndex;
 	}
@@ -90,13 +88,22 @@ class String {
 		var ret = -1;
 		if (startIndex == null)
 			startIndex = length;
-		while (true) {
-			var p = indexOf(str, ret + 1);
-			if (p == -1 || p > startIndex || p == ret)
-				break;
-			ret = p;
+		if (str == "") {
+			if (this == "") {
+				return 0;
+			} else {
+				var max = cast Math.max(startIndex, 0);
+				return cast Math.min(length, max);
+			}
+		} else {
+			while (true) {
+				var p = indexOf(str, ret + 1);
+				if (p == -1 || p > startIndex || p == ret)
+					break;
+				ret = p;
+			}
+			return ret;
 		}
-		return ret;
 	}
 
 	public function split(delimiter:String):Array<String> {

+ 15 - 6
std/neko/_std/String.hx

@@ -57,6 +57,11 @@
 	}
 
 	public function indexOf(str:String, ?startIndex:Int):Int {
+		var startIndex = startIndex == null || startIndex < 0 ? 0 : startIndex;
+		if (str.length == 0) {
+			var min = startIndex > length ? length : startIndex;
+			return min < 0 ? 0 : min;
+		}
 		untyped {
 			var l = __dollar__ssize(this.__s);
 			if (startIndex == null || startIndex < -l)
@@ -76,6 +81,11 @@
 	}
 
 	public function lastIndexOf(str:String, ?startIndex:Int):Int {
+		if (str.length == 0) {
+			var startIndex = startIndex == null ? length : startIndex;
+			var min = startIndex > length ? length : startIndex;
+			return min < 0 ? 0 : min;
+		}
 		untyped {
 			var last = -1;
 			var l = __dollar__ssize(this.__s);
@@ -212,10 +222,9 @@
 		return new String(untyped __dollar__string(s) + this.__s);
 	}
 
-	public static function fromCharCode(code:Int):String
-		untyped {
-			var s = __dollar__smake(1);
-			__dollar__sset(s, 0, code);
-			return new String(s);
-		}
+	public static function fromCharCode(code:Int):String untyped {
+		var s = __dollar__smake(1);
+		__dollar__sset(s, 0, code);
+		return new String(s);
+	}
 }

+ 24 - 28
std/php/Boot.hx

@@ -107,7 +107,7 @@ class Boot {
 		Check if specified property has getter
 	**/
 	public static function hasGetter(phpClassName:String, property:String):Bool {
-		if(!ensureLoaded(phpClassName))
+		if (!ensureLoaded(phpClassName))
 			return false;
 
 		var has = false;
@@ -124,7 +124,7 @@ class Boot {
 		Check if specified property has setter
 	**/
 	public static function hasSetter(phpClassName:String, property:String):Bool {
-		if(!ensureLoaded(phpClassName))
+		if (!ensureLoaded(phpClassName))
 			return false;
 
 		var has = false;
@@ -148,7 +148,8 @@ class Boot {
 		Retrieve metadata for specified class
 	**/
 	public static function getMeta(phpClassName:String):Null<Dynamic> {
-		if(!ensureLoaded(phpClassName)) return null;
+		if (!ensureLoaded(phpClassName))
+			return null;
 		return Global.isset(meta[phpClassName]) ? meta[phpClassName] : null;
 	}
 
@@ -283,7 +284,7 @@ class Boot {
 	**/
 	@:pure(false)
 	static public function isPhpKeyword(str:String):Bool {
-		//The body of this method is generated by the compiler
+		// The body of this method is generated by the compiler
 		return false;
 	}
 
@@ -596,7 +597,7 @@ class Boot {
 		if (result != null) {
 			return result;
 		}
-		if(!Global.method_exists(obj, methodName) && !Global.isset(Syntax.field(obj, methodName))) {
+		if (!Global.method_exists(obj, methodName) && !Global.isset(Syntax.field(obj, methodName))) {
 			return null;
 		}
 		result = new HxClosure(obj, methodName);
@@ -769,19 +770,13 @@ private class HxString {
 	}
 
 	public static function indexOf(str:String, search:String, startIndex:Int = null):Int {
-		if (startIndex == null) {
+		if (search.length == 0) {
+			return Global.max(0, Global.min(startIndex == null ? 0 : startIndex, str.length));
+		}
+		if (startIndex == null || startIndex < 0) {
 			startIndex = 0;
-		} else {
-			var length = str.length;
-			if (startIndex < 0) {
-				startIndex += length;
-				if (startIndex < 0) {
-					startIndex = 0;
-				}
-			}
-			if (startIndex >= length && search != '') {
-				return -1;
-			}
+		} else if (startIndex >= str.length) {
+			return -1;
 		}
 		var index:EitherType<Int, Bool> = if (search == '') {
 			var length = str.length;
@@ -793,9 +788,15 @@ private class HxString {
 	}
 
 	public static function lastIndexOf(str:String, search:String, startIndex:Int = null):Int {
+		if (search.length == 0) {
+			return Global.max(0, Global.min(startIndex == null ? str.length : startIndex, str.length));
+		}
 		var start = startIndex;
 		if (start == null) {
 			start = 0;
+		}
+		if (startIndex == null) {
+			startIndex = 0;
 		} else {
 			var length = str.length;
 			if (start >= 0) {
@@ -809,7 +810,8 @@ private class HxString {
 		}
 		var index:EitherType<Int, Bool> = if (search == '') {
 			var length = str.length;
-			startIndex == null || startIndex > length ? length : startIndex;
+			startIndex == null
+			|| startIndex > length ? length : startIndex;
 		} else {
 			Global.mb_strrpos(str, search, start);
 		}
@@ -942,17 +944,14 @@ private class HxDynamicStr extends HxClosure {
 
 /**
 	Anonymous objects implementation
-**/
-@:keep
-@:dox(hide)
-private class HxAnon extends StdClass {
+**/ @:keep @:dox(hide) private class HxAnon extends StdClass {
 	public function new(fields:NativeArray = null) {
 		super();
 		if (fields != null) {
 			Syntax.foreach(fields, function(name, value) Syntax.setField(this, name, value));
 		}
 	}
-	
+
 	@:phpMagic
 	function __get(name:String) {
 		return null;
@@ -966,10 +965,7 @@ private class HxAnon extends StdClass {
 
 /**
 	Closures implementation
-**/
-@:keep
-@:dox(hide)
-private class HxClosure {
+**/ @:keep @:dox(hide) private class HxClosure {
 	/** `this` for instance methods; php class name for static methods */
 	var target:Dynamic;
 
@@ -1023,4 +1019,4 @@ private class HxClosure {
 	public function callWith(newThis:Dynamic, args:NativeArray):Dynamic {
 		return Global.call_user_func_array(getCallback(newThis), args);
 	}
-}
+}

+ 12 - 2
std/python/_std/sys/net/Socket.hx

@@ -31,6 +31,7 @@ import python.lib.socket.Socket in PSocket;
 import python.lib.Socket in PSocketModule;
 import python.lib.socket.Address in PAddress;
 import python.lib.Select;
+import python.lib.ssl.Errors;
 
 private class SocketInput extends haxe.io.Input {
 	var __s:PSocket;
@@ -55,7 +56,11 @@ private class SocketInput extends haxe.io.Input {
 		var r;
 		var data = buf.getData();
 		try {
-			r = __s.recv(len, 0);
+			try {
+				r = __s.recv(len, 0);
+			} catch(e:SSLWantReadError) {
+				return 0;
+			}
 			for (i in pos...(pos + r.length)) {
 				data.set(i, r[i - pos]);
 			}
@@ -93,7 +98,12 @@ private class SocketOutput extends haxe.io.Output {
 		try {
 			var data = buf.getData();
 			var payload = python.Syntax.code("{0}[{1}:{1}+{2}]", data, pos, len);
-			var r = __s.send(payload, 0);
+			var r = 0;
+			try {
+				r = __s.send(payload, 0);
+			} catch(e:SSLWantWriteError) {
+				return 0;
+			}
 			return r;
 		} catch (e:BlockingIOError) {
 			throw Blocked;

+ 22 - 11
std/python/internal/StringImpl.hx

@@ -42,14 +42,18 @@ class StringImpl {
 	}
 
 	@:ifFeature("dynamic_read.lastIndexOf", "anon_optional_read.lastIndexOf", "python.internal.StringImpl.lastIndexOf")
-	public static inline function lastIndexOf(s:String, str:String, ?startIndex:Int):Int {
-		if (startIndex == null) {
+	public static function lastIndexOf(s:String, str:String, ?startIndex:Int):Int {
+		if (str == "") {
+			var i = startIndex == null ? s.length : startIndex;
+			return UBuiltins.max(0, UBuiltins.min(i, s.length));
+		} else if (startIndex == null) {
 			return Syntax.callField(s, "rfind", str, 0, s.length);
-		} else if(str == "") {
+		} else if (str == "") {
 			var length = s.length;
-			if(startIndex < 0) {
+			if (startIndex < 0) {
 				startIndex = length + startIndex;
-				if(startIndex < 0) startIndex = 0;
+				if (startIndex < 0)
+					startIndex = 0;
 			}
 			return startIndex > length ? length : startIndex;
 		} else {
@@ -75,19 +79,26 @@ class StringImpl {
 	}
 
 	@:ifFeature("dynamic_read.indexOf", "anon_optional_read.indexOf", "python.internal.StringImpl.indexOf")
-	public static inline function indexOf(s:String, str:String, ?startIndex:Int) {
-		if (startIndex == null)
+	public static function indexOf(s:String, str:String, ?startIndex:Int) {
+		if (str == "") {
+			var i = startIndex == null ? 0 : startIndex;
+			return UBuiltins.max(0, UBuiltins.min(i, s.length));
+		} else if (startIndex == null || startIndex < 0)
 			return Syntax.callField(s, "find", str);
-		else
+		else if (startIndex >= s.length) {
+			return -1;
+		} else {
 			return indexOfImpl(s, str, startIndex);
+		}
 	}
 
 	static function indexOfImpl(s:String, str:String, startIndex:Int) {
-		if(str == "") {
+		if (str == "") {
 			var length = s.length;
-			if(startIndex < 0) {
+			if (startIndex < 0) {
 				startIndex = length + startIndex;
-				if(startIndex < 0) startIndex = 0;
+				if (startIndex < 0)
+					startIndex = 0;
 			}
 			return startIndex > length ? length : startIndex;
 		}

+ 29 - 0
std/python/lib/ssl/Errors.hx

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package python.lib.ssl;
+
+@:pythonImport("ssl", "SSLWantReadError")
+extern class SSLWantReadError {}
+
+@:pythonImport("ssl", "SSLWantWriteError")
+extern class SSLWantWriteError {}

+ 1 - 0
tests/misc/java/projects/Issue11014/Main.hx

@@ -1,3 +1,4 @@
+@:functionalInterface
 interface MathOperation {
 	function perform(a:Int, b:Int):Int;
 }

+ 4 - 4
tests/misc/java/projects/Issue11014/compile.hxml.stdout

@@ -1,4 +1,4 @@
-Main.hx:17: Add: 12
-Main.hx:20: Subtract: 4
-Main.hx:23: Multiply: 32
-Main.hx:28: Divide: 2
+Main.hx:18: Add: 12
+Main.hx:21: Subtract: 4
+Main.hx:24: Multiply: 32
+Main.hx:29: Divide: 2

+ 1 - 0
tests/misc/java/projects/Issue11054/pack/ItemGroupEvents.java

@@ -1,6 +1,7 @@
 package pack;
 
 public final class ItemGroupEvents {
+	@FunctionalInterface
 	public interface ModifyEntries {
 		void modifyEntries(int entries);
 	}

+ 16 - 6
tests/runci/targets/Lua.hx

@@ -48,7 +48,7 @@ class Lua {
 
 		getLuaDependencies();
 
-		for (lv in ["-l5.1", "-l5.2", "-l5.3"].concat(systemName == 'Linux' && Linux.arch == Arm64 ? [] : ["-j2.0", "-j2.1"])) {
+		for (lv in ["-l5.1", "-l5.2", "-l5.3", "-l5.4"].concat(systemName == 'Linux' && Linux.arch == Arm64 ? [] : ["-j2.0", "-j2.1"])) {
 			final envpath = getInstallPath() + '/lua_env/lua$lv';
 			addToPATH(envpath + '/bin');
 
@@ -73,23 +73,33 @@ class Lua {
 			installLib("luasec", "1.0.2-1");
 
 			installLib("lrexlib-pcre2", "2.9.1-1");
-			installLib("luv", "1.36.0-0");
 			installLib("luasocket", "3.0rc1-2");
-			installLib("luautf8", "0.1.1-1");
 
 			//Install bit32 for lua 5.1
-			if(lv == "-l5.1"){
+			if (lv == "-l5.1")
 				installLib("bit32", "5.2.2-1");
+
+			if (lv == "-l5.4") {
+				installLib("bit32", "5.3.5.1-1");
+				installLib("luv", "1.44.2-1");
+				installLib("luautf8", "0.1.5-2");
+			} else {
+				installLib("luv", "1.36.0-0");
+				installLib("luautf8", "0.1.1-1");
 			}
 
 			installLib("hx-lua-simdjson", "0.0.1-1");
 
 			changeDirectory(unitDir);
-			runCommand("haxe", ["compile-lua.hxml"].concat(args));
+			final luaDefine = if (lv.startsWith("-l")) {
+				lv.replace("-l", "lua").replace(".", "_");
+			} else lv.replace("-j", "luajit").replace(".", "_");
+			final luaVer = ["-D", luaDefine];
+			runCommand("haxe", ["compile-lua.hxml"].concat(args).concat(luaVer));
 			runCommand("lua", ["bin/unit.lua"]);
 
 			changeDirectory(sysDir);
-			runCommand("haxe", ["compile-lua.hxml"].concat(args));
+			runCommand("haxe", ["compile-lua.hxml"].concat(args).concat(luaVer));
 			runSysTest("lua", ["bin/lua/sys.lua"]);
 
 			changeDirectory(getMiscSubDir("luaDeadCode", "stringReflection"));

+ 2 - 1
tests/unit/src/unit/TestResource.hx

@@ -18,7 +18,8 @@ class TestResource extends Test {
 		#if (neko || php || eval)
 		// allow binary strings
 		eq(haxe.Resource.getBytes("re/s?!%[]))(\"'1.bin").sub(0, 9).toHex(), "48656c6c6f2c204927");
-		#else
+		// updated luautf8.len returns nil for data with invalid sequences
+		#elseif !lua5_4
 		// cut until first \0
 		eq(haxe.Resource.getString("re/s?!%[]))(\"'1.bin").substr(0, 2), "He");
 		#end

+ 3 - 1
tests/unit/src/unit/issues/Issue11054.hx

@@ -10,10 +10,12 @@ private abstract class Robot<T> {
 	}
 }
 
+@:functionalInterface
 private interface IGreetRobot {
 	function greet<T>(robot:Robot<T>):Void;
 }
 
+@:functionalInterface
 private interface IMathOperation {
 	function operate(a:Int, b:Int):Int;
 }
@@ -51,7 +53,7 @@ class Issue11054 extends Test {
 		});
 
 		var called = false;
-		robot2.performTask(function(target) {
+		robot2.performTask(function(target:Robot<Dynamic>) {
 			called = true;
 		});
 		t(called);

+ 30 - 0
tests/unit/src/unit/issues/Issue11236.hx

@@ -0,0 +1,30 @@
+package unit.issues;
+
+#if jvm
+import haxe.Int64;
+import java.lang.Runnable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+private final exec = Executors.newSingleThreadScheduledExecutor();
+private var called = false;
+
+private function schedule(f:() -> Void)
+	exec.schedule(f, 0, TimeUnit.MILLISECONDS);
+
+private function greeter():Void {
+	called = true;
+	exec.shutdown();
+}
+#end
+
+class Issue11236 extends Test {
+	#if jvm
+	function test() {
+		schedule(greeter);
+
+		t(exec.awaitTermination(Int64.ofInt(1), TimeUnit.SECONDS));
+		t(called);
+	}
+	#end
+}

+ 70 - 0
tests/unit/src/unit/issues/Issue5271.hx

@@ -0,0 +1,70 @@
+package unit.issues;
+
+/**
+ * Tests if String's indexOf/lastIndexOf functions behave ECMAScript compliant
+ */
+class Issue5271 extends unit.Test {
+	#if !flash
+	function test() {
+		/*
+		 * test indexOf
+		 */
+		eq(0, "".indexOf(""));
+		eq(0, "".indexOf("", 0));
+		eq(0, "".indexOf("", 1));
+		eq(0, "".indexOf("", -1));
+
+		eq(0, " ".indexOf(""));
+		eq(0, " ".indexOf("", 0));
+		eq(1, " ".indexOf("", 1));
+		eq(1, " ".indexOf("", 2));
+		eq(0, " ".indexOf("", -1));
+
+		eq(0, "dog".indexOf(""));
+		eq(0, "dog".indexOf("", 0));
+		eq(1, "dog".indexOf("", 1));
+		eq(2, "dog".indexOf("", 2));
+		eq(3, "dog".indexOf("", 3));
+		eq(3, "dog".indexOf("", 4));
+		eq(3, "dog".indexOf("", 10));
+		eq(0, "dog".indexOf("", -1));
+
+		eq(-1, "dogdog".indexOf("cat"));
+		eq(3, "dogcat".indexOf("cat"));
+		eq(3, "dogcat".indexOf("cat", 0));
+		eq(3, "dogcat".indexOf("cat", 1));
+		eq(3, "catcat".indexOf("cat", 3));
+		eq(-1, "catcat".indexOf("cat", 4));
+
+		/*
+		 * test lastIndexOf
+		 */
+		eq(0, "".lastIndexOf(""));
+		eq(0, "".lastIndexOf("", 0));
+		eq(0, "".lastIndexOf("", 1));
+		eq(0, "".lastIndexOf("", -1));
+
+		eq(1, " ".lastIndexOf(""));
+		eq(0, " ".lastIndexOf("", 0));
+		eq(1, " ".lastIndexOf("", 1));
+		eq(1, " ".lastIndexOf("", 2));
+		eq(0, " ".lastIndexOf("", -1));
+
+		eq(3, "dog".lastIndexOf(""));
+		eq(0, "dog".lastIndexOf("", 0));
+		eq(1, "dog".lastIndexOf("", 1));
+		eq(2, "dog".lastIndexOf("", 2));
+		eq(3, "dog".lastIndexOf("", 3));
+		eq(3, "dog".lastIndexOf("", 4));
+		eq(3, "dog".lastIndexOf("", 10));
+		eq(0, "dog".lastIndexOf("", -1));
+
+		eq(-1, "dogdog".lastIndexOf("cat"));
+		eq(3, "dogcat".lastIndexOf("cat"));
+		eq(-1, "dogcat".lastIndexOf("cat", 0));
+		eq(-1, "dogcat".lastIndexOf("cat", 1));
+		eq(3, "catcat".lastIndexOf("cat", 3));
+		eq(3, "catcat".lastIndexOf("cat", 4));
+	}
+	#end
+}

+ 268 - 0
tests/unit/src/unit/issues/Issue6705.hx

@@ -0,0 +1,268 @@
+package unit.issues;
+
+class Issue6705 extends unit.Test {
+	function memberFunction() {}
+
+	static function staticFunction() {}
+
+	function memberFunction1(i:Int) {}
+
+	static function staticFunction1(i:Int) {}
+
+	@:pure(false) static function alias<T>(t:T)
+		return t;
+
+	@:pure(false) static function equalsT<T>(a:T, b:T)
+		return a == b;
+
+	function test() {
+		function localFunction() {}
+
+		var localClosure = localFunction;
+		var memberClosure = memberFunction;
+		var staticClosure = staticFunction;
+
+		t(localFunction == alias(localFunction));
+		t(localFunction == alias(localClosure));
+		#if !neko
+		t(memberFunction == alias(memberFunction));
+		t(memberFunction == alias(memberClosure));
+		#end
+		t(staticFunction == alias(staticFunction));
+		t(staticFunction == alias(staticClosure));
+		t(localClosure == alias(localClosure));
+		t(memberClosure == alias(memberClosure));
+		t(staticClosure == alias(staticClosure));
+		t(localFunction == alias(localFunction));
+
+		t(equalsT(localFunction, localClosure));
+		#if !neko
+		t(equalsT(memberFunction, memberFunction));
+		t(equalsT(memberFunction, memberClosure));
+		#end
+		t(equalsT(staticFunction, staticFunction));
+		t(equalsT(staticFunction, staticClosure));
+		t(equalsT(localClosure, localClosure));
+		t(equalsT(memberClosure, memberClosure));
+		t(equalsT(staticClosure, staticClosure));
+
+		t(Reflect.compareMethods(localFunction, alias(localFunction)));
+		t(Reflect.compareMethods(localFunction, alias(localClosure)));
+		t(Reflect.compareMethods(memberFunction, alias(memberFunction)));
+		t(Reflect.compareMethods(memberFunction, alias(memberClosure)));
+		t(Reflect.compareMethods(staticFunction, alias(staticFunction)));
+		t(Reflect.compareMethods(staticFunction, alias(staticClosure)));
+		t(Reflect.compareMethods(localClosure, alias(localClosure)));
+		t(Reflect.compareMethods(memberClosure, alias(memberClosure)));
+		t(Reflect.compareMethods(staticClosure, alias(staticClosure)));
+
+		var array = [localFunction, memberFunction, staticFunction];
+		eq(0, array.indexOf(localFunction));
+		#if !neko
+		eq(1, array.indexOf(memberFunction));
+		#end
+		eq(2, array.indexOf(staticFunction));
+	}
+
+	function testButEverythingIsDynamic() {
+		function localFunction() {}
+
+		var localClosure:Dynamic = localFunction;
+		var memberClosure:Dynamic = memberFunction;
+		var staticClosure:Dynamic = staticFunction;
+
+		t((localFunction : Dynamic) == alias((localFunction : Dynamic)));
+		t((localFunction : Dynamic) == alias(localClosure));
+		#if !neko
+		t((memberFunction : Dynamic) == alias((memberFunction : Dynamic)));
+		t((memberFunction : Dynamic) == alias(memberClosure));
+		#end
+		t((staticFunction : Dynamic) == alias((staticFunction : Dynamic)));
+		t((staticFunction : Dynamic) == alias(staticClosure));
+		t(localClosure == alias(localClosure));
+		t(memberClosure == alias(memberClosure));
+		t(staticClosure == alias(staticClosure));
+		t((localFunction : Dynamic) == alias((localFunction : Dynamic)));
+
+		t(equalsT((localFunction : Dynamic), localClosure));
+		#if !neko
+		t(equalsT((memberFunction : Dynamic), (memberFunction : Dynamic)));
+		t(equalsT((memberFunction : Dynamic), memberClosure));
+		#end
+		t(equalsT((staticFunction : Dynamic), (staticFunction : Dynamic)));
+		t(equalsT((staticFunction : Dynamic), staticClosure));
+		t(equalsT(localClosure, localClosure));
+		t(equalsT(memberClosure, memberClosure));
+		t(equalsT(staticClosure, staticClosure));
+
+		t(Reflect.compareMethods((localFunction : Dynamic), alias((localFunction : Dynamic))));
+		t(Reflect.compareMethods((localFunction : Dynamic), alias(localClosure)));
+		t(Reflect.compareMethods((memberFunction : Dynamic), alias((memberFunction : Dynamic))));
+		t(Reflect.compareMethods((memberFunction : Dynamic), alias(memberClosure)));
+		t(Reflect.compareMethods((staticFunction : Dynamic), alias((staticFunction : Dynamic))));
+		t(Reflect.compareMethods((staticFunction : Dynamic), alias(staticClosure)));
+		t(Reflect.compareMethods(localClosure, alias(localClosure)));
+		t(Reflect.compareMethods(memberClosure, alias(memberClosure)));
+		t(Reflect.compareMethods(staticClosure, alias(staticClosure)));
+
+		var array = [(localFunction : Dynamic), (memberFunction : Dynamic), (staticFunction : Dynamic)];
+		eq(0, array.indexOf((localFunction : Dynamic)));
+		#if !neko
+		eq(1, array.indexOf((memberFunction : Dynamic)));
+		#end
+		eq(2, array.indexOf((staticFunction : Dynamic)));
+	}
+
+	function testButEverythingIsBackwards() {
+		function localFunction() {}
+
+		var localClosure = localFunction;
+		var memberClosure = memberFunction;
+		var staticClosure = staticFunction;
+
+		t(alias(localFunction) == localFunction);
+		t(alias(localClosure) == localFunction);
+		#if !neko
+		t(alias(memberFunction) == memberFunction);
+		t(alias(memberClosure) == memberFunction);
+		#end
+		t(alias(staticFunction) == staticFunction);
+		t(alias(staticClosure) == staticFunction);
+		t(alias(localClosure) == localClosure);
+		t(alias(memberClosure) == memberClosure);
+		t(alias(staticClosure) == staticClosure);
+		t(alias(localFunction) == localFunction);
+
+		t(equalsT(localClosure, localFunction));
+		#if !neko
+		t(equalsT(memberFunction, memberFunction));
+		t(equalsT(memberClosure, memberFunction));
+		#end
+		t(equalsT(staticFunction, staticFunction));
+		t(equalsT(staticClosure, staticFunction));
+		t(equalsT(localClosure, localClosure));
+		t(equalsT(memberClosure, memberClosure));
+		t(equalsT(staticClosure, staticClosure));
+
+		t(Reflect.compareMethods(alias(localFunction), localFunction));
+		t(Reflect.compareMethods(alias(localClosure), localFunction));
+		t(Reflect.compareMethods(alias(memberFunction), memberFunction));
+		t(Reflect.compareMethods(alias(memberClosure), memberFunction));
+		t(Reflect.compareMethods(alias(staticFunction), staticFunction));
+		t(Reflect.compareMethods(alias(staticClosure), staticFunction));
+		t(Reflect.compareMethods(alias(localClosure), localClosure));
+		t(Reflect.compareMethods(alias(memberClosure), memberClosure));
+		t(Reflect.compareMethods(alias(staticClosure), staticClosure));
+	}
+
+	function testButEverythingIsBackwardsAndDynamic() {
+		function localFunction() {}
+
+		var localClosure:Dynamic = localFunction;
+		var memberClosure:Dynamic = memberFunction;
+		var staticClosure:Dynamic = staticFunction;
+
+		t(alias((localFunction : Dynamic)) == (localFunction : Dynamic));
+		t(alias(localClosure) == (localFunction : Dynamic));
+		#if !neko
+		t(alias((memberFunction : Dynamic)) == (memberFunction : Dynamic));
+		t(alias(memberClosure) == (memberFunction : Dynamic));
+		#end
+		t(alias((staticFunction : Dynamic)) == (staticFunction : Dynamic));
+		t(alias(staticClosure) == (staticFunction : Dynamic));
+		t(alias(localClosure) == localClosure);
+		t(alias(memberClosure) == memberClosure);
+		t(alias(staticClosure) == staticClosure);
+		t(alias((localFunction : Dynamic)) == (localFunction : Dynamic));
+
+		t(equalsT(localClosure, (localFunction : Dynamic)));
+		#if !neko
+		t(equalsT((memberFunction : Dynamic), (memberFunction : Dynamic)));
+		t(equalsT(memberClosure, (memberFunction : Dynamic)));
+		#end
+		t(equalsT((staticFunction : Dynamic), (staticFunction : Dynamic)));
+		t(equalsT(staticClosure, (staticFunction : Dynamic)));
+		t(equalsT(localClosure, localClosure));
+		t(equalsT(memberClosure, memberClosure));
+		t(equalsT(staticClosure, staticClosure));
+
+		t(Reflect.compareMethods(alias((localFunction : Dynamic)), (localFunction : Dynamic)));
+		t(Reflect.compareMethods(alias(localClosure), (localFunction : Dynamic)));
+		t(Reflect.compareMethods(alias((memberFunction : Dynamic)), (memberFunction : Dynamic)));
+		t(Reflect.compareMethods(alias(memberClosure), (memberFunction : Dynamic)));
+		t(Reflect.compareMethods(alias((staticFunction : Dynamic)), (staticFunction : Dynamic)));
+		t(Reflect.compareMethods(alias(staticClosure), (staticFunction : Dynamic)));
+		t(Reflect.compareMethods(alias(localClosure), localClosure));
+		t(Reflect.compareMethods(alias(memberClosure), memberClosure));
+		t(Reflect.compareMethods(alias(staticClosure), staticClosure));
+	}
+
+	function test1() {
+		function localFunction1(i:Int) {}
+
+		var localClosure1 = localFunction1;
+		var memberClosure1 = memberFunction1;
+		var staticClosure1 = staticFunction1;
+
+		t(localFunction1 == alias(localFunction1));
+		t(localFunction1 == alias(localClosure1));
+		#if !neko
+		t(memberFunction1 == alias(memberFunction1));
+		t(memberFunction1 == alias(memberClosure1));
+		#end
+		t(staticFunction1 == alias(staticFunction1));
+		t(staticFunction1 == alias(staticClosure1));
+		t(localClosure1 == alias(localClosure1));
+		t(memberClosure1 == alias(memberClosure1));
+		t(staticClosure1 == alias(staticClosure1));
+
+		t(equalsT(localFunction1, localFunction1));
+		t(equalsT(localFunction1, localClosure1));
+		#if !neko
+		t(equalsT(memberFunction1, memberFunction1));
+		t(equalsT(memberFunction1, memberClosure1));
+		#end
+		t(equalsT(staticFunction1, staticFunction1));
+		t(equalsT(staticFunction1, staticClosure1));
+		t(equalsT(localClosure1, localClosure1));
+		t(equalsT(memberClosure1, memberClosure1));
+		t(equalsT(staticClosure1, staticClosure1));
+
+		t(Reflect.compareMethods(localFunction1, alias(localFunction1)));
+		t(Reflect.compareMethods(localFunction1, alias(localClosure1)));
+		t(Reflect.compareMethods(memberFunction1, alias(memberFunction1)));
+		t(Reflect.compareMethods(memberFunction1, alias(memberClosure1)));
+		t(Reflect.compareMethods(staticFunction1, alias(staticFunction1)));
+		t(Reflect.compareMethods(staticFunction1, alias(staticClosure1)));
+		t(Reflect.compareMethods(localClosure1, alias(localClosure1)));
+		t(Reflect.compareMethods(memberClosure1, alias(memberClosure1)));
+		t(Reflect.compareMethods(staticClosure1, alias(staticClosure1)));
+
+		var array = [localFunction1, memberFunction1, staticFunction1];
+		eq(0, array.indexOf(localFunction1));
+		#if !neko
+		eq(1, array.indexOf(memberFunction1));
+		#end
+		eq(2, array.indexOf(staticFunction1));
+	}
+
+	#if !neko
+	function testCallsEqualityCheck_tempvarCallExprs() {
+		var callCount = 0;
+		function getFn():() -> Void {
+			callCount++;
+			return memberFunction;
+		}
+		t(getFn() == getFn());
+		eq(2, callCount);
+	}
+	#end
+
+	#if !hl // @see https://github.com/HaxeFoundation/haxe/issues/10031
+	function testTypeChange() {
+		function f1(x:Float) {}
+		var f2:Int->Void = f1;
+		t(f1 == f2);
+	}
+	#end
+}

+ 46 - 37
tests/unit/src/unitstd/String.unit.hx

@@ -2,21 +2,18 @@
 var str = "foo";
 var str2 = new String(str);
 str == str2;
-
 // toUpperCase
 "foo".toUpperCase() == "FOO";
 "_bar".toUpperCase() == "_BAR";
 "123b".toUpperCase() == "123B";
 "".toUpperCase() == "";
 "A".toUpperCase() == "A";
-
 // toLowerCase
 "FOO".toLowerCase() == "foo";
 "_BAR".toLowerCase() == "_bar";
 "123B".toLowerCase() == "123b";
 "".toLowerCase() == "";
 "a".toLowerCase() == "a";
-
 // charAt
 var s = "foo1bar";
 s.charAt(0) == "f";
@@ -27,11 +24,10 @@ s.charAt(4) == "b";
 s.charAt(5) == "a";
 s.charAt(6) == "r";
 s.charAt(7) == "";
-s.charAt( -1) == "";
+s.charAt(-1) == "";
 "".charAt(0) == "";
 "".charAt(1) == "";
-"".charAt( -1) == "";
-
+"".charAt(-1) == "";
 // charCodeAt
 var s = "foo1bar";
 s.charCodeAt(0) == 102;
@@ -42,8 +38,7 @@ s.charCodeAt(4) == 98;
 s.charCodeAt(5) == 97;
 s.charCodeAt(6) == 114;
 s.charCodeAt(7) == null;
-s.charCodeAt( -1) == null;
-
+s.charCodeAt(-1) == null;
 // code
 "f".code == 102;
 "o".code == 111;
@@ -51,7 +46,6 @@ s.charCodeAt( -1) == null;
 "b".code == 98;
 "a".code == 97;
 "r".code == 114;
-
 // indexOf
 var s = "foo1bar";
 s.indexOf("") == 0;
@@ -62,25 +56,47 @@ s.indexOf("b") == 4;
 s.indexOf("a") == 5;
 s.indexOf("r") == 6;
 s.indexOf("z") == -1;
-//s.indexOf(null) == -1;
-//s.indexOf(null, 1) == -1;
-//s.indexOf(null, -1) == -1;
+// empty string
+s.indexOf("") == 0;
+s.indexOf("", -1) == 0;
+s.indexOf("", 0) == 0;
+s.indexOf("", 1) == 1;
+s.indexOf("", 2) == 2;
+s.indexOf("", 3) == 3;
+s.indexOf("", 4) == 4;
+s.indexOf("", 5) == 5;
+s.indexOf("", 6) == 6;
+s.indexOf("", 7) == 7;
+s.indexOf("", 8) == 7;
+// negative startIndex
+s.indexOf("f", -1) == 0;
+s.indexOf("o", -1) == 1;
+s.indexOf("1", -1) == 3;
+s.indexOf("b", -1) == 4;
+s.indexOf("a", -1) == 5;
+s.indexOf("r", -1) == 6;
+s.indexOf("z", -1) == -1;
+// startIndex >= length
+s.indexOf("f", 7) == -1;
+s.indexOf("o", 7) == -1;
+s.indexOf("1", 7) == -1;
+s.indexOf("b", 7) == -1;
+s.indexOf("a", 7) == -1;
+s.indexOf("r", 7) == -1;
+s.indexOf("z", 7) == -1;
+// s.indexOf(null) == -1;
+// s.indexOf(null, 1) == -1;
+// s.indexOf(null, -1) == -1;
 s.indexOf("foo") == 0;
 s.indexOf("oo") == 1;
-//s.indexOf("bart") == -1;
-//s.indexOf("r", -1) == -1;
-//s.indexOf("r", -10) == -1;
+// s.indexOf("bart") == -1;
 s.indexOf("", 2) == 2;
 s.indexOf("", 200) == s.length;
 s.indexOf("o", 1) == 1;
 s.indexOf("o", 2) == 2;
 s.indexOf("o", 3) == -1;
-//s.indexOf("", -10) == 0;
-//s.indexOf("", 7) == 7; // see #8117
-//s.indexOf("", 8) == -1; // see #8117
 s.indexOf("r", 7) == -1;
 s.indexOf("r", 8) == -1;
-
 // lastIndexOf
 var s = "foofoofoobarbar";
 s.lastIndexOf("") == s.length;
@@ -95,9 +111,9 @@ s.lastIndexOf("barb") == 9;
 s.lastIndexOf("barb", 12) == 9;
 s.lastIndexOf("barb", 13) == 9;
 s.lastIndexOf("z") == -1;
-//s.lastIndexOf(null) == -1;
-//s.lastIndexOf(null, 1) == -1;
-//s.lastIndexOf(null, 14) == -1;
+// s.lastIndexOf(null) == -1;
+// s.lastIndexOf(null, 1) == -1;
+// s.lastIndexOf(null, 14) == -1;
 s.lastIndexOf("", 2) == 2;
 s.lastIndexOf("", 200) == s.length;
 s.lastIndexOf("r", 14) == 14;
@@ -111,14 +127,14 @@ s.lastIndexOf("bar", 9) == 9;
 s.lastIndexOf("bar", 8) == -1;
 s.lastIndexOf("a", s.length) == 13;
 s.lastIndexOf("a", s.length + 9000) == 13;
-
 // split
 var s = "xfooxfooxxbarxbarxx";
-s.split("x") == ["", "foo", "foo", "", "bar", "bar", "",""];
-s.split("xx") == ["xfooxfoo","barxbar",""];
-s.split("") == ["x", "f", "o", "o", "x", "f", "o", "o", "x", "x", "b", "a", "r", "x", "b", "a", "r", "x", "x"];
+s.split("x") == ["", "foo", "foo", "", "bar", "bar", "", ""];
+s.split("xx") == ["xfooxfoo", "barxbar", ""];
+s.split("") == [
+	"x", "f", "o", "o", "x", "f", "o", "o", "x", "x", "b", "a", "r", "x", "b", "a", "r", "x", "x"
+];
 s.split("z") == ["xfooxfooxxbarxbarxx"];
-
 // substr
 var s = "xfooxfooxxbarxbarxx";
 s.substr(0) == "xfooxfooxxbarxbarxx";
@@ -130,17 +146,16 @@ s.substr(-1) == "x";
 s.substr(-2) == "xx";
 s.substr(-18) == "fooxfooxxbarxbarxx";
 s.substr(-19) == "xfooxfooxxbarxbarxx";
-s.substr( -100) == "xfooxfooxxbarxbarxx";
+s.substr(-100) == "xfooxfooxxbarxbarxx";
 s.substr(0, 0) == "";
 s.substr(0, 1) == "x";
 s.substr(0, 2) == "xf";
 s.substr(0, 100) == "xfooxfooxxbarxbarxx";
 s.substr(0, -1) == "xfooxfooxxbarxbarx";
 s.substr(0, -2) == "xfooxfooxxbarxbar";
-//s.substr(1, -2) == "fooxfooxxbarxbar";
-//s.substr(2, -2) == "ooxfooxxbarxbar";
+// s.substr(1, -2) == "fooxfooxxbarxbar";
+// s.substr(2, -2) == "ooxfooxxbarxbar";
 s.substr(0, -100) == "";
-
 // substring
 var s = "xfooxfooxxbarxbarxx";
 s.substring(0, 0) == "";
@@ -165,31 +180,25 @@ s.substring(100, 0) == "xfooxfooxxbarxbarxx";
 s.substring(120, 100) == "";
 s.substring(5, 8) == "foo";
 s.substring(8, 5) == "foo";
-
 // fromCharCode
 String.fromCharCode(65) == "A";
-
 // ensure int strings compared as strings, not parsed ints (issue #3734)
 ("3" > "11") == true;
 (" 3" < "3") == true;
-
 // string comparison (see #8332)
 ("a" < "b") == true;
 ("a" <= "b") == true;
 ("a" > "b") == false;
 ("a" >= "b") == false;
-
 #if target.unicode
 ("𠜎zя" > "abя") == true;
 ("𠜎zя" >= "abя") == true;
 ("𠜎zя" < "abя") == false;
 ("𠜎zя" <= "abя") == false;
-
 #if target.utf16
 // since U+10002 in UTF16 is D800 DC02
 ("\u{FF61}" < "\u{10002}") == false;
 #else
 ("\u{FF61}" < "\u{10002}") == true;
 #end
-
 #end

+ 22 - 27
tests/unit/src/unitstd/UnicodeString.unit.hx

@@ -4,30 +4,26 @@ var codes = [132878, 122, 1103];
 
 // length
 s.length == codes.length;
-
 // // toUpperCase, toLowerCase
 // var turkishLower = "ğüşıiöç";
 // var turkishUpper = "ĞÜŞIİÖÇ";
 // turkishUpper == turkishLower.toUpperCase();
 // turkishLower == turkishUpper.toLowerCase();
-
 // charAt
 s.charAt(0) == "𠜎";
 s.charAt(1) == "z";
 s.charAt(2) == "я";
 s.charAt(3) == "";
-s.charAt( -1) == "";
-("":UnicodeString).charAt(0) == "";
-("":UnicodeString).charAt(1) == "";
-("":UnicodeString).charAt( -1) == "";
-
+s.charAt(-1) == "";
+("" : UnicodeString).charAt(0) == "";
+("" : UnicodeString).charAt(1) == "";
+("" : UnicodeString).charAt(-1) == "";
 // charCodeAt
 s.charCodeAt(0) == codes[0];
 s.charCodeAt(1) == codes[1];
 s.charCodeAt(2) == codes[2];
 s.charCodeAt(3) == null;
 s.charCodeAt(-1) == null;
-
 // indexOf
 var s:UnicodeString = "𠜎zяяw";
 s.indexOf("𠜎") == 0;
@@ -43,15 +39,22 @@ s.indexOf("я", 2) == 2;
 s.indexOf("я", 3) == 3;
 s.indexOf("я", 4) == -1;
 s.indexOf("я", 40) == -1;
-#if !lua // TODO https://github.com/HaxeFoundation/haxe/pull/8370
-s.indexOf("я", -1) == -1;
-s.indexOf("я", -2) == 3;
+s.indexOf("я", -1) == 2;
+s.indexOf("я", -2) == 2;
 s.indexOf("я", -3) == 2;
 s.indexOf("я", -4) == 2;
 s.indexOf("я", -5) == 2;
 s.indexOf("я", -50) == 2;
-#end
-
+// empty string
+s.indexOf("") == 0;
+s.indexOf("", -1) == 0;
+s.indexOf("", 0) == 0;
+s.indexOf("", 1) == 1;
+s.indexOf("", 2) == 2;
+s.indexOf("", 3) == 3;
+s.indexOf("", 4) == 4;
+s.indexOf("", 5) == 5;
+s.indexOf("", 6) == 5;
 // lastIndexOf
 var s:UnicodeString = "𠜎zяяw";
 s.lastIndexOf("𠜎") == 0;
@@ -66,7 +69,6 @@ s.lastIndexOf("я", 2) == 2;
 s.lastIndexOf("я", 3) == 3;
 s.lastIndexOf("я", 4) == 3;
 s.lastIndexOf("я", 40) == 3;
-
 // substr
 var s:UnicodeString = "𠜎zяяw";
 s.substr(0) == "𠜎zяяw";
@@ -86,7 +88,6 @@ s.substr(0, 100) == "𠜎zяяw";
 s.substr(0, -1) == "𠜎zяя";
 s.substr(0, -2) == "𠜎zя";
 s.substr(0, -100) == "";
-
 // substring
 var s:UnicodeString = "𠜎zяяw";
 s.substring(0, 0) == "";
@@ -112,7 +113,6 @@ s.substring(100, 0) == "𠜎zяяw";
 s.substring(120, 100) == "";
 s.substring(1, 4) == "zяя";
 s.substring(4, 1) == "zяя";
-
 var s = new UnicodeString("𠜎zя");
 
 // @:op(UnicodeString)
@@ -126,7 +126,6 @@ s2 <= s;
 (s + s2).length == s.length + s2.length;
 var s3 = s;
 (s3 += s2).length == s.length + s2.length;
-
 // @:op(String)
 var s2 = "abя";
 s != s2;
@@ -135,19 +134,16 @@ s > s2;
 s >= s2;
 s2 < s;
 s2 <= s;
-(s + s2).length == s.length + (s2:UnicodeString).length;
+(s + s2).length == s.length + (s2 : UnicodeString).length;
 var s3 = s;
-(s3 += s2).length == s.length + (s2:UnicodeString).length;
-
+(s3 += s2).length == s.length + (s2 : UnicodeString).length;
 // iterator
-aeq(codes, [for(c in s) c]);
-
+aeq(codes, [for (c in s) c]);
 // keyValueIterator
-var keys = [for(i in 0...codes.length) i];
-var actualKeyCodes = [for(i => c in s) [i, c]];
+var keys = [for (i in 0...codes.length) i];
+var actualKeyCodes = [for (i => c in s) [i, c]];
 aeq(keys, actualKeyCodes.map(a -> a[0]));
 aeq(codes, actualKeyCodes.map(a -> a[1]));
-
 // validate
 UnicodeString.validate(haxe.io.Bytes.ofHex("f0a9b8bde38182c3ab61"), UTF8) == true;
 UnicodeString.validate(haxe.io.Bytes.ofHex("ed9fbf"), UTF8) == true;
@@ -158,7 +154,6 @@ UnicodeString.validate(haxe.io.Bytes.ofHex("c0af"), UTF8) == false; // overlong
 UnicodeString.validate(haxe.io.Bytes.ofHex("eda080"), UTF8) == false; // surrogate byte sequence
 UnicodeString.validate(haxe.io.Bytes.ofHex("edbfbf"), UTF8) == false; // surrogate byte sequence
 UnicodeString.validate(haxe.io.Bytes.ofHex("f4908080"), UTF8) == false; // U+110000
-
 #else
 1 == 1;
-#end
+#end